Java EE Concurrency API

مرحبا بالجميع!

وهنا ننغمس في الكعك ونطلق الدفق الثاني من الدورة التدريبية "Java Enterprise Developer" . كتب المنشئ والمعلم الدائم للدورة - فيتالي إيفانوف ، مقالة حول هذا الموضوع حتى ، والتي نأمل أن تبدو مفيدة لك :)

لذا دعنا نذهب :)

تستكشف هذه المقالة واجهة برمجة تطبيقات مواصفات JavaEE ( JSR 236 ) ، التي تحدد معيار المهام المتوازية في حاوية JavaEE باستخدام مفهوم الموارد المُدارة. جعل إصدار الإصدار السابع من JavaEE من الممكن تشغيل المهام الموازية في حاويات Enterprise ، مما وفر للمطور أدوات وأدوات مساعدة ملائمة للعمل مع المهام المتعددة. حتى تلك اللحظة ، تم ترك جميع المهام المتعددة للتنفيذ المحدد لخادم التطبيق المستخدم ، والذي يقرر بشكل مستقل تحسين المهام. تم اعتبار انتهاك هذا المبدأ ممارسة سيئة في بناء بنية تطبيقات المؤسسة. ونتيجة لذلك ، لم يُنصح المطور بإنشاء سلاسل رسائل جديدة ، وفي بعض الأحيان تم حظر مثل هذا السلوك على مستوى الحاوية.


يجب ألا تحاول وحدة برامج المؤسسة ادارة سلاسل العمليات. يجب ألا تحاول وحدة المؤسسة بدء سلسلة رسائل أو إيقافها أو تعليقها أو استئنافها ، أو تغيير أولوية سلسلة الرسائل أو اسمها. يجب ألا تحاول وحدة برامج المؤسسة ادارة مجموعات سلاسل العمليات.

(الترجمة المجانية للمؤلف: يجب ألا تحاول EJBs إدارة سلاسل المحادثات ، أي محاولة بدء تنفيذها وإيقافها وإيقافها مؤقتًا واستعادتها ، أو تغيير الأولوية أو تغيير اسم سلسلة المحادثات. أيضًا ، يجب ألا تحاول EJBs إدارة مجموعات سلاسل المحادثات).

في الواقع ، يعد حظر إنشاء سلاسل الرسائل الخاصة بها في حاويات JavaEE أمرًا صعبًا للغاية ، ولكن مع هذا النهج ، لا يمكن لخدمات الخلفية للحاوية ضمان صحة عملهم. على سبيل المثال ، من المحتمل أن يعمل إغلاق معاملة عند الانتهاء من طريقة EJB بشكل غير صحيح إذا تم بدء المهام في سلسلة رسائل جديدة باستخدام سلاسل ورث (أو تطبيقات قابلة للتنفيذ) من JavaSE. أيضًا ، سيؤدي استخدام أنواع الواجهة الأساسية التي توفرها واجهة برمجة التطبيقات التنفيذية ، مثل ExecutorService و ScheduledExecutorService ، عند إنشائها من خلال الأساليب الثابتة لفئة التنفيذيين ، إلى أخطاء محتملة وتعطيل تنفيذ خدمات الحاوية.

من بين الأدوات التي أوصت بها مواصفات JavaEE للتنفيذ غير المتزامن للمهام ، كان على المطور استخدام EJBs غير المتزامنة / Statefull و / أو الفاصوليا الموجهة بالرسائل ، والتي تكون قدراتها كافية لمجموعة معينة من المهام ، والأهم من ذلك ، يتم التحكم في إدارتها بشكل كامل وكامل من قبل خادم التطبيق ، وهي حاوية EJB.

ومع ذلك ، كما ذكرنا سابقًا ، وبفضل JSR 236 ، ظهرت الموارد التي تدار بالحاويات والتي تنفذ دعمًا لتنفيذ المهام المتعددة وغير المتزامنة ، مما يوسع قدرات الحزمة java.util.concurrent من JavaSE. بالنسبة إلى مكدس JavaEE ، توجد فئات الموارد المُدارة في حزمة javax.enterprise.concurrent ، ويتم توفير الوصول إلى كائنات هذه الفئات من خلال إدخال الموارد باستخدام التعليق التوضيحي @Resource ، أو من خلال سياق JNDI (على وجه الخصوص ، InitialContext). في الوقت نفسه ، تمت إضافة إمكانيات استخدام كائنات Future / ScheduledFuture / CompletableFuture المألوفة لبيئة متعددة الخيوط داخل تطبيقات JavaEE.

لذلك ، هناك كلمات كافية ونلقي نظرة فاحصة على كل من الموارد المدارة التي توفرها المواصفات من وجهة نظر عملية ، وبالتحديد في سياق استخدام التطبيق في رمز التطبيق ، وكذلك من وجهة نظر تكوين الموارد باستخدام خادم التطبيق Glassfish 5 كمثال.

حسنًا ، كانت فئة ManagedExecutorService هي الأولى التي يتم أخذها في الاعتبار ، والتي (تفهم بالفعل من الاسم) توسع قدرات JavaSE المألوفة خارج الصندوق ExecutorService ومصممة للتنفيذ غير المتزامن للمهام في بيئة JavaEE.

لتهيئة هذا النوع من ExecutorService ليس فقط داخل خادم تطبيق Glassfish ، يجب عليك الرجوع إلى ملف التكوين domain.xml ، والذي يتم تحديد موقعه بواسطة الدليل $ {GLASSFISH_HOME} / domains / <domainname> / config. يتم عرض جزء من هذا الملف أدناه:

 <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> 

الانتقال إلى واجهة لوحة تحكم Glassfish 5 ، تكوين

ManagedExecutorService هي كما يلي:



يسمح هذا القسم بإنشاء موارد جديدة من نفس النوع ، وإدارة الموارد الموجودة ، والحذف ، وكذلك القفل والفتح.

لمحبي إدارة وحدة التحكم في Glassfish ، يتم تقديم أداة asadmin قوية ، باستخدام أمر إنشاء مدير تنفيذي ، يمكنك إنشاء موارد ManagedExecutorService جديدة:



في رمز التطبيق ، من الملائم أكثر استخدام حقن الموارد للحصول على مرجع إلى الكائن الذي تم إنشاؤه بواسطة ManagedExecutorService ، ولكن يمكنك أيضًا استخدام أدوات JNDI ، كما هو موضح أدناه:

 @Resource(lookup = "concurrent/OtusExecutorService") ManagedExecutorService executor; InitialContext context = new InitialContext(); ManagedExecutorService managedExecutorServiceWithContext = (ManagedExecutorService) context.lookup( "concurrent/OtusExecutorService"); 

أود أن @Resource انتباه القارئ إلى حقيقة أن معلمة البحث اختيارية @Resource التوضيحي @Resource ، وإذا لم يتم تعريفها بواسطة المطور في رمز التطبيق ، فإن الحاوية تضخ الموارد الافتراضية مع بادئة __default في __default . في هذه الحالة ، بالنسبة للمطور ، يصبح الرمز أكثر إيجازًا:

 @Resource ManagedExecutorService executor; 

بعد تلقي مرجع لهذا الكائن ، باستخدام طرق execute() و submit() ، يمكنك تشغيل المهام التي تنفذ واجهة Runnable أو Callable داخل الحاوية.

بالانتقال إلى مثال ، أود أن أشير إلى أنه من بين مجموعة متنوعة من الحالات المحتملة ، فإن الأكثر إثارة للاهتمام هي المهام التي يتم تنفيذها في بيئة JavaEE الموزعة والتي من المهم فيها تقديم دعم المعاملات في بيئة متعددة الخيوط. كما تعلم ، لقد طورت JavaEE مواصفات JTA (Java Transaction API) ، والتي تسمح لك بتحديد حدود المعاملة من خلال بدءها صراحةً بطريقة البداية begin() وتنتهي بأساليب commit() ، أو تنفيذ التغييرات ، أو rollback() ، والتي تتراجع عن الإجراءات المنفذة.

ضع في اعتبارك مثالاً على مهمة تُرجع رسالة من قائمة تضم مائة عنصر حسب الفهرس في معاملة المستخدم:

 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 () { … } } 

كود servlet الذي يعرض رسالة من الكشف في فهرس تم اختياره عشوائيا:

 @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(); } } } 

التالي هو المورد ManagedScheduledExecutorService ، والغرض الرئيسي منه هو تخطيط المهام التي تتكرر على فترات منتظمة أو تتطلب تأجيل التنفيذ.



من وجهة نظر تهيئة هذا المورد من خلال وحدة تحكم المشرف الخاصة بـ GlassFish ، لم يتم العثور على تغييرات خاصة مقارنة بالنوع السابق:



لإنشاء مورد من النوع ManagedScheduledExecutorService asadmin ، لدى asadmin أمر create-managed-scheduled-executor-service



في كود التطبيق ، ما زلنا نستخدم حقن الموارد:

 @Resource(lookup = "concurrent/OtusScheduledExecutorService") ManagedScheduledExecutorService scheduledExecutor; 

الطرق الرئيسية لتنفيذ المهام لهذا النوع من خدمات ExecutorService هي schedule() ، الذي يتلقى مهام من النوع القابل للتشغيل أو القابل للاستدعاء كمدخل ، و scheduleAtFixedRate() AtFixedRate scheduleAtFixedRate() ، والذي يحدد أيضًا التأخير الأولي في المهمة ويعين الفاصل الزمني للتكرار في TimeUnit (الثواني والدقائق وما إلى ذلك) .).

يمكن إعادة كتابة الحالة السابقة على النحو التالي:

 @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); // Wait } catch (InterruptedException e) { e.printStackTrace(); } } try { response.getWriter().write("Callable task received message with following content '" + futureResult.get() + "'"); } catch ( Exception e) { e.printStackTrace(); } } } 

بالإضافة إلى ذلك ، توفر واجهة برمجة تطبيقات Concurrency لبيئة Enterpise القدرة على إنشاء تدفقات يتم التحكم فيها. بالنسبة لهذه المهام ، يجب عليك استخدام إمكانات مصنع سلسلة الرسائل المُدارة الذي يقوم بتنفيذ وظائفه من خلال فئة ManagedThreadFactory التي تحمل نفس الاسم ويتم الوصول إليها أيضًا من خلال خدمة JNDI:

 @Resource ManagedThreadFactory factory; 

تبدو النافذة الإدارية لوحدة تحكم Glassfish "قديمة الطراز":



باستخدام مصنع سلسلة رسائل مُدار ، يصبح من الممكن ليس فقط تزويد الحاوية بآليات التحكم في التدفق ، ولكن أيضًا لتهيئة خصائص سلاسل العمليات التي تم إنشاؤها: تعيين الأسماء وتحديد الأولويات ، والتي يمكن أن تبسط في المستقبل بشكل كبير البحث عن المشاكل عند تحليل تفريغ سلسلة الرسائل عن طريق الكشف بسهولة عن ترتيب تنفيذ سلاسل الرسائل التي تم تسميتها سابقًا.

في حالتنا ، نحدد فئة الدفق الذي يعرض معلومات حول صديق ترتبط هذه المهمة به بشكل لا يمكن فصله عن وحدة التحكم:

 public class SimpleThreadTask implements Runnable { private String friend; public SimpleThreadTask(String friend){ this.friend = friend; } @Override public void run() { System.out.println("Hello, " + friend); } } 

دع servlet يبدأ سلسلة المحادثات وأبلغ هذا الإخراج:

 @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."); } } 

بالانتقال إلى الميزة النهائية لـ JavaEE في مجال تعدد مؤشرات الترابط - Context Services ، تجدر الإشارة إلى أن هذه الخدمات تنشئ كائنات وكيل سياقية ديناميكية. نحن جميعًا على دراية بقدرات الوكلاء الديناميكيين من JavaSE ( java.lang.reflect.Proxy ) ، والتي تسمح لك بإنشاء تطبيق ديناميكي للواجهات المطلوبة ، والتي يتم استخدام قدراتها بنشاط لإنشاء اتصالات قاعدة البيانات وإدارة المعاملات ، يتم استخدامها لجميع أنواع اعتراضات AOP ، إلخ. علاوة على ذلك ، بالنسبة للخوادم الوكيلة التي تم إنشاؤها من خلال خدمات سياق JavaEE ، فمن المفترض أنها يمكن أن تعمل في إطار سياق JNDI المشترك ، وسياق الأمان ، وفئة الحاوية.

لتوصيل الخدمة ، ما عليك سوى استخدام الرمز:

 @Resource ContextService service; 

من وجهة نظر إدارة هذا المورد وتكوينه ، كل شيء مألوف للغاية ومشابه للأنواع التي تم النظر فيها بالفعل:



فيما يلي مثال لمؤشر ترابط يبدأ مهمة وكيل في سياق الحاوية:

 public class SampleProxyTask implements Runnable { @Override public void run() { //  Subject subject = Subject.getSubject(AccessController.getContext()); logInfo(subject.getPrincipals()); //    calculateSmth(); } private void calculateSmth() { … } private void logInfo(Set<Principal> subject) { … } } 

فول EJB عديم الحالة لإنشاء وكلاء سياقية:

 @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); } } 

وأخيرًا ، كود servlet الذي يقوم بتنفيذ المهمة:

 @WebServlet("/context") public class ContextServiceServlet extends HttpServlet { @Inject ContextServiceBean contextServiceBean; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { contextServiceBean.perform(new SampleProxyTask()); } } 

هذا ينهي بالفعل قدرات مطور JavaEE للعمل في بيئة متعددة الخيوط. بفضلهم ، ستكون جميع العمليات والخدمات التي تحدث في الحاوية تحت السيطرة الصارمة للخادم ، وتنسيق عملهم وعدم انتهاك أمر التنفيذ المعتاد. بالنسبة لأهداف مطور المؤسسة ، غالبًا ما تكون هذه الإمكانات كافية وفي الإصدار الثامن لم تتغير واجهة برمجة التطبيقات هذه.

النهاية

كما هو الحال دائمًا ، نحن في انتظار الأسئلة والتعليقات ونحرص على مراجعة Vitaly للحصول على درس مفتوح ، حيث يمكنه أيضًا طرح الأسئلة والاستماع / المشاركة في موضوع "CDI قيد التنفيذ @

Source: https://habr.com/ru/post/ar421809/


All Articles