Halo semuanya!
Dan di sini kita
menikmati roti dan meluncurkan aliran kedua tentu saja
"Pengembang Java Enterprise" . Pencipta dan guru tetap kursus -
Vitaly Ivanov , menulis sebuah artikel tentang hal ini bahkan, yang, kami harap, akan tampak bermanfaat bagi Anda :)
Jadi ayo pergi :)
Artikel ini mengeksplorasi JavaEE Concurrency Specification API (
JSR 236 ), yang menetapkan standar untuk tugas paralel dalam wadah JavaEE menggunakan konsep sumber daya yang dikelola. Rilis JavaEE versi ketujuh memungkinkan untuk menjalankan tugas paralel dalam wadah Enterprise, menyediakan pengembang dengan alat yang mudah digunakan dan utilitas untuk bekerja dengan multitasking. Hingga saat itu, semua tugas multitasking diserahkan kepada implementasi spesifik dari server aplikasi yang digunakan, yang secara mandiri menentukan optimalisasi tugas. Pelanggaran prinsip ini dianggap sebagai praktik buruk dalam membangun arsitektur aplikasi Enterprise. Akibatnya, pengembang tidak disarankan untuk membuat utas baru, dan terkadang perilaku seperti itu di tingkat kontainer dilarang.
Kacang perusahaan tidak boleh mencoba mengelola utas. Bean perusahaan tidak boleh mencoba memulai, menghentikan, menangguhkan, atau melanjutkan utas, atau mengubah prioritas atau nama utas. Bean perusahaan tidak boleh mencoba mengelola grup utas.(terjemahan gratis penulis: EJB tidak boleh mencoba mengelola utas, yaitu, mencoba memulai, menghentikan, menjeda, dan mengembalikan eksekusi mereka, atau mengubah prioritas atau mengubah nama utas. Juga, EJB tidak boleh mencoba mengelola grup utas).Bahkan, untuk melarang pembuatan thread mereka sendiri dalam wadah JavaEE cukup bermasalah, namun, dengan pendekatan ini, layanan latar belakang wadah tidak dapat menjamin kebenaran pekerjaan mereka. Misalnya, menutup transaksi setelah menyelesaikan metode EJB berpotensi dapat bekerja secara salah jika tugas dimulai di utas baru menggunakan pewaris Threads (atau implementasi Runnable) dari JavaSE. Juga, menggunakan tipe antarmuka dasar yang disediakan oleh API Pelaksana, seperti ExecutorService dan ScheduledExecutorService, ketika dibuat melalui metode statis dari kelas Pelaksana, akan menyebabkan potensi kesalahan dan mengganggu pelaksanaan layanan kontainer.
Dari alat-alat yang direkomendasikan oleh spesifikasi JavaEE untuk pelaksanaan tugas yang tidak sinkron, pengembang harus menggunakan EJB Stateless / Statefull asynchronous dan / atau kacang yang didorong Pesan, kemampuan yang cukup untuk berbagai tugas tertentu, dan yang paling penting, manajemen yang awalnya sepenuhnya dan sepenuhnya dikontrol oleh server aplikasi, yaitu wadah EJB.
Namun, seperti yang disebutkan sebelumnya, berkat
JSR 236 , sumber daya yang dikelola wadah telah muncul yang mengimplementasikan dukungan untuk pelaksanaan tugas multithreading dan asinkron, memperluas kemampuan paket
java.util.concurrent
dari JavaSE. Untuk tumpukan JavaEE, kelas sumber daya yang dikelola terletak di paket
javax.enterprise.concurrent
, dan akses ke objek kelas ini disediakan melalui injeksi sumber daya menggunakan anotasi
@Resource
, atau melalui konteks JNDI (khususnya, InitialContext). Pada saat yang sama, kemungkinan menggunakan objek Future / ScheduledFuture / CompletableFuture yang akrab dengan lingkungan multi-threaded di dalam aplikasi JavaEE telah ditambahkan.
Jadi, ada cukup lirik dan mari kita lihat lebih dekat masing-masing sumber daya yang dikelola yang disediakan oleh spesifikasi dari sudut pandang praktis, yaitu dalam konteks menggunakan aplikasi dalam kode aplikasi, serta dari sudut pandang konfigurasi sumber daya menggunakan server aplikasi Glassfish 5 sebagai contoh.
Yah, kelas ManagedExecutorService adalah yang pertama kali dipertimbangkan, yang (sudah memahami dari namanya) memperluas kemampuan JavaSE yang sudah dikenal di luar kotak ExecutorService dan dirancang untuk pelaksanaan tugas yang tidak sinkron dalam lingkungan JavaEE.
Untuk mengonfigurasi tidak hanya jenis ExecutorService di dalam server aplikasi Glassfish, Anda harus merujuk ke file konfigurasi domain.xml, yang lokasinya ditentukan oleh direktori $ {GLASSFISH_HOME} / domains / <domainname> / config. Sebuah fragmen dari file ini disajikan di bawah ini:
<domain application-root="${com.sun.aas.instanceRoot}/applications" version="25" log-root="${com.sun.aas.instanceRoot}/logs"> <resources> <context-service object-type="system-all" jndi-name="concurrent/__defaultContextService" /> <managed-executor-service object-type="system-all" jndi-name="concurrent/__defaultManagedExecutorService" /> <managed-scheduled-executor-service object-type="system-all" jndi-name="concurrent/__defaultManagedScheduledExecutorService" /> <managed-thread-factory object-type="system-all" jndi-name="concurrent/__defaultManagedThreadFactory" /> </resources> <servers> <server config-ref="server-config" name="server"> <resource-ref ref="concurrent/__defaultContextService" /> <resource-ref ref="concurrent/__defaultManagedExecutorService" /> <resource-ref ref="concurrent/__defaultManagedScheduledExecutorService" /> <resource-ref ref="concurrent/__defaultManagedThreadFactory" /> </server> </servers> </domain>
Masuk ke antarmuka panel admin Glassfish 5, konfigurasi
ManagedExecutorService adalah sebagai berikut:

Bagian ini memungkinkan pembuatan sumber daya baru dari jenis yang sama, pengelolaan sumber daya yang ada, penghapusan, serta mengunci dan membuka kunci.
Untuk penggemar administrasi konsol di Glassfish, utilitas asadmin yang kuat disajikan, menggunakan perintah
create-managed-executor-service
di dalamnya, Anda dapat membuat sumber daya ManagedExecutorService baru:

Dalam kode aplikasi, lebih mudah menggunakan injeksi sumber daya untuk mendapatkan referensi ke objek yang dibuat oleh ManagedExecutorService, tetapi Anda juga dapat menggunakan alat JNDI, seperti yang ditunjukkan di bawah ini:
@Resource(lookup = "concurrent/OtusExecutorService") ManagedExecutorService executor; InitialContext context = new InitialContext(); ManagedExecutorService managedExecutorServiceWithContext = (ManagedExecutorService) context.lookup( "concurrent/OtusExecutorService");
Saya ingin menarik perhatian pembaca pada fakta bahwa parameter pencarian adalah opsional untuk penjelasan
@Resource
, dan jika tidak ditentukan oleh pengembang dalam kode aplikasi, wadah menyuntikkan sumber daya default dengan awalan
__default
dalam
__default
mereka. Dalam hal ini, untuk pengembang, kode menjadi lebih ringkas:
@Resource ManagedExecutorService executor;
Setelah menerima referensi ke objek ini, menggunakan metode
execute()
dan
submit()
, Anda bisa menjalankan tugas dengan mengimplementasikan antarmuka Runnable atau Callable di dalam wadah.
Beralih ke contoh, saya ingin mencatat bahwa di antara seluruh variasi kasus yang mungkin, yang paling menarik adalah tugas yang dilakukan dalam lingkungan JavaEE terdistribusi dan di mana penting untuk memberikan dukungan transaksional dalam lingkungan multi-threaded. Seperti yang Anda ketahui, JavaEE telah mengembangkan spesifikasi JTA (Java Transaction API), yang memungkinkan Anda untuk menentukan batas-batas transaksi dengan memulainya secara eksplisit dengan metode
begin()
dan diakhiri dengan metode
commit()
, melakukan perubahan, atau
rollback()
, yang memutar kembali tindakan yang dilakukan.
Pertimbangkan contoh tugas yang mengembalikan pesan dari daftar seratus item berdasarkan indeks dalam transaksi pengguna:
public class TransactionSupportCallableTask implements Callable<String> { private int messageIndex; public TransactionSupportCallableTask(int messageId) { this. messageIndex = messageId; } public String call() { UserTransaction tx = lookupUserTransaction(); String message = null; try { tx.begin(); message = getMessage(messageIndex); tx.commit(); } catch (Exception e) { e.printStackTrace(); try { tx.rollback(); } catch (Exception e1) { e1.printStackTrace(); } } return message; } private void getMessage(int index) { … } private UserTransaction lookupUserTransaction () { … } }
Kode untuk servlet yang menampilkan pesan dari daftar pada indeks yang dipilih secara acak:
@WebServlet("/task") public class ManagedExecutorServiceServlet extends HttpServlet { @Resource(lookup = "concurrent/OtusExecutorService") ManagedExecutorService executor; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Future<String> futureResult = executor.submit(new TransactionSupportCallableTask(Random.nextInt(100))); while (!futureResult.isDone()) { try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } } try { response.getWriter().write("Callable task has received message with following content '" + futureResult.get() + "'"); } catch(Exception e) { e.printStackTrace(); } } }
Berikutnya yang akan dipertimbangkan adalah sumber daya ManagedScheduledExecutorService, tujuan utamanya adalah merencanakan tugas yang diulang secara berkala atau membutuhkan eksekusi yang tertunda.

Dari sudut pandang mengkonfigurasi sumber daya ini melalui konsol admin GlassFish, tidak ada perubahan khusus yang ditemukan dibandingkan dengan jenis sebelumnya:

Untuk dengan cepat membuat sumber daya dari tipe ManagedScheduledExecutorService,
asadmin
memiliki perintah
create-managed-scheduled-executor-service

Dalam kode aplikasi, kami masih menggunakan injeksi sumber daya:
@Resource(lookup = "concurrent/OtusScheduledExecutorService") ManagedScheduledExecutorService scheduledExecutor;
Metode utama untuk menjalankan tugas untuk jenis ExecutorService ini adalah
schedule()
, yang menerima tugas dari jenis Runnable atau Callable sebagai input, dan
scheduleAtFixedRate()
, yang juga menentukan penundaan awal dalam tugas dan menetapkan interval pengulangan dalam TimeUnit (detik, menit, dll.) .).
Kasus sebelumnya dapat ditulis ulang sebagai berikut:
@WebServlet("/scheduledTask") public class ManagedScheduledExecutorServiceServlet extends HttpServlet { @Resource(lookup = "concurrent/OtusScheduledExecutorService") ManagedScheduledExecutorService scheduledExecutor; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ScheduledFuture<String> futureResult = scheduledExecutor.schedule( new TransactionSupportCallableTask(Random.nextInt(100)), 5, TimeUnit.SECONDS); while (!futureResult.isDone()) { try { Thread.sleep(50);
Juga, Concurrency API untuk lingkungan Enterpise menyediakan kemampuan untuk membuat aliran terkontrol. Untuk tugas-tugas ini, Anda harus menggunakan kemampuan pabrik thread terkelola yang mengimplementasikan fungsinya melalui kelas ManagedThreadFactory dengan nama yang sama dan juga diakses melalui layanan JNDI:
@Resource ManagedThreadFactory factory;
Jendela administrasi untuk konsol Glassfish terlihat "kuno":

Dengan menggunakan pabrik thread yang dikelola, menjadi mungkin tidak hanya untuk menyediakan wadah dengan mekanisme kontrol aliran, tetapi juga untuk menginisialisasi properti dari thread yang dihasilkan: menetapkan nama dan memprioritaskan, yang di masa depan dapat sangat menyederhanakan pencarian masalah ketika mengurai dump thread dengan mudah mendeteksi urutan eksekusi dari thread yang disebutkan sebelumnya.
Dalam kasus kami, kami mendefinisikan kelas aliran yang menampilkan informasi tentang seorang teman yang terkait dengan tugas ini tanpa terpisahkan dengan konsol:
public class SimpleThreadTask implements Runnable { private String friend; public SimpleThreadTask(String friend){ this.friend = friend; } @Override public void run() { System.out.println("Hello, " + friend); } }
Biarkan servlet memulai utas dan laporkan ke output:
@WebServlet("/thread") public class ManagedThreadFactoryServlet extends HttpServlet { @Resource ManagedThreadFactory factory; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Thread thread = factory.newThread(new SimpleThreadTask("Otus")); thread.setName("ManagedThreadFromPool"); thread.setPriority(7); thread.start(); response.getWriter().write("Custom thread has been running."); } }
Beralih ke fitur terakhir JavaEE di bidang multithreading - Context Services, harus dicatat bahwa layanan ini membuat objek proxy kontekstual yang dinamis. Kita semua akrab dengan kemampuan proxy dinamis dari JavaSE (
java.lang.reflect.Proxy
), yang memungkinkan Anda untuk menghasilkan implementasi dinamis dari antarmuka yang diperlukan, yang kemampuannya secara aktif digunakan untuk membuat koneksi database dan manajemen transaksi, digunakan untuk semua jenis pencegat AOP, dll. Selain itu, untuk proxy yang dibuat melalui layanan konteks JavaEE, diasumsikan bahwa mereka dapat bekerja dalam kerangka konteks JNDI yang umum, konteks keamanan, dan kelas kontainer.
Untuk menghubungkan layanan, cukup gunakan kode:
@Resource ContextService service;
Dari sudut pandang mengelola dan mengkonfigurasi sumber daya ini, semuanya sangat akrab dan mirip dengan jenis yang sudah dipertimbangkan:

Di bawah ini adalah contoh utas yang memulai tugas proksi dalam konteks wadah:
public class SampleProxyTask implements Runnable { @Override public void run() {
Kacang EJB tanpa kewarganegaraan untuk membuat proksi kontekstual:
@Stateless public class ContextServiceBean { @Resource ContextService service; @Resource ManagedExecutorService executor; public void perform(Runnable task) { Runnable proxy = service.createContextualProxy(task, Runnable.class); executor.submit(proxy); } }
Dan akhirnya, kode untuk servlet menjalankan tugas:
@WebServlet("/context") public class ContextServiceServlet extends HttpServlet { @Inject ContextServiceBean contextServiceBean; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { contextServiceBean.perform(new SampleProxyTask()); } }
Ini sebenarnya mengakhiri kemampuan pengembang JavaEE untuk bekerja di lingkungan multi-utas. Berkat mereka, semua proses dan layanan yang terjadi di wadah akan berada di bawah kendali ketat server, mengoordinasikan pekerjaan mereka dan tidak melanggar perintah eksekusi yang biasa. Untuk tujuan pengembang Enterprise, kapabilitas ini seringkali cukup dan dalam versi kedelapan API ini belum berubah.
AKHIR
Seperti biasa, kami menunggu pertanyaan dan komentar dan pastikan untuk memeriksa
Vitaly untuk
pelajaran terbuka , di mana ia juga dapat mengajukan pertanyaan dan mendengarkan / berpartisipasi dalam topik “CDI dalam aksi @