JavaRush /مدونة جافا /Random-AR /لا يمكنك إفساد جافا بخيط: الجزء الأول - الخيوط
Viacheslav
مستوى

لا يمكنك إفساد جافا بخيط: الجزء الأول - الخيوط

نشرت في المجموعة

مقدمة

لقد تم دمج خاصية Multithreading في لغة Java منذ أيامها الأولى. لذلك دعونا نلقي نظرة سريعة على ماهية تعدد العمليات. لا يمكنك تدمير جافا بخيط: الجزء الأول - الخيوط - 1لنأخذ الدرس الرسمي من Oracle كنقطة بداية: " الدرس: تطبيق "Hello World! ". دعونا نغير رمز تطبيق Hello World الخاص بنا قليلًا إلى ما يلي:
class HelloWorldApp {
    public static void main(String[] args) {
        System.out.println("Hello, " + args[0]);
    }
}
argsعبارة عن مجموعة من معلمات الإدخال التي يتم تمريرها عند بدء تشغيل البرنامج. لنحفظ هذا الرمز في ملف باسم يطابق اسم الفئة والامتداد .java. لنقم بالتجميع باستخدام الأداة المساعدة javac : javac HelloWorldApp.java بعد ذلك، قم باستدعاء الكود الخاص بنا مع بعض المعلمات، على سبيل المثال، Roger: java HelloWorldApp Roger لا يمكنك تدمير جافا بخيط: الجزء الأول - الخيوط - 2الكود الخاص بنا به الآن عيب خطير. إذا لم نمرر أي وسيطة (أي فقط قمنا بتنفيذ Java HelloWorldApp)، فسوف نحصل على خطأ:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
        at HelloWorldApp.main(HelloWorldApp.java:3)
حدث استثناء (أي خطأ) في موضوع اسمه main. اتضح أن هناك نوعًا من المواضيع في جافا؟ هذا هو المكان الذي تبدأ رحلتنا.

جافا والخيوط

لفهم ما هو مؤشر الترابط، عليك أن تفهم كيفية تشغيل تطبيق Java. دعونا نغير الكود الخاص بنا كما يلي:
class HelloWorldApp {
    public static void main(String[] args) {
		while (true) {
			//Do nothing
		}
	}
}
الآن دعونا نجمعها مرة أخرى باستخدام javac. بعد ذلك، من أجل الراحة، سنقوم بتشغيل كود Java الخاص بنا في نافذة منفصلة. على نظام التشغيل Windows، يمكنك القيام بذلك على النحو التالي: start java HelloWorldApp. الآن، باستخدام الأداة المساعدة jps ، لنرى ما هي المعلومات التي ستخبرنا بها Java: لا يمكنك تدمير جافا بخيط: الجزء الأول - الخيوط - 3الرقم الأول هو PID أو معرف العملية، وهو معرف العملية. ما هي العملية؟
Процесс — это совокупность codeа и данных, разделяющих общее виртуальное addressное пространство.
بمساعدة العمليات، يتم عزل تنفيذ البرامج المختلفة عن بعضها البعض: يستخدم كل تطبيق منطقة الذاكرة الخاصة به دون التدخل في البرامج الأخرى. أنصحك بقراءة المقال بمزيد من التفصيل: " https://habr.com/post/164487/ ". لا يمكن أن توجد العملية بدون سلاسل عمليات، لذلك إذا كانت العملية موجودة، فهذا يعني وجود مؤشر ترابط واحد على الأقل فيها. كيف يحدث هذا في جافا؟ عندما نقوم بتشغيل برنامج Java، يبدأ تنفيذه بالملحق main. نحن ندخل إلى البرنامج نوعًا ما، لذلك mainتسمى هذه الطريقة الخاصة بنقطة الدخول، أو "نقطة الدخول". يجب أن تكون الطريقة mainدائمًا public static voidحتى يتمكن Java Virtual Machine (JVM) من البدء في تنفيذ برنامجنا. راجع " لماذا تعتبر طريقة Java الرئيسية ثابتة؟ " لمزيد من التفاصيل. اتضح أن مشغل Java (java.exe أو javaw.exe) هو تطبيق بسيط (تطبيق C بسيط): يقوم بتحميل ملفات DLL المختلفة، والتي هي في الواقع JVM. يقوم مشغل Java بإجراء مجموعة محددة من مكالمات Java Native Interface (JNI). JNI هي الآلية التي تربط بين عالم Java Virtual Machine وعالم C++. اتضح أن المشغل ليس JVM، بل هو المُحمل الخاص به. إنه يعرف الأوامر الصحيحة التي يجب تنفيذها لبدء JVM. يعرف كيفية تنظيم كل البيئة اللازمة باستخدام مكالمات JNI. يتضمن تنظيم البيئة أيضًا إنشاء سلسلة رسائل رئيسية تسمى عادةً main. لرؤية المواضيع الموجودة في عملية Java بشكل أكثر وضوحًا، نستخدم برنامج jvisualvm ، المضمن في JDK. بمعرفة معرف عملية ما، يمكننا فتح البيانات عليها على الفور: jvisualvm --openpid айдипроцесса لا يمكنك تدمير جافا بخيط: الجزء الأول - الخيوط - 4ومن المثير للاهتمام أن كل مؤشر ترابط له منطقة منفصلة خاصة به في الذاكرة المخصصة للعملية. تسمى بنية الذاكرة هذه بالمكدس. المكدس يتكون من إطارات. الإطار هو نقطة استدعاء الطريقة ونقطة التنفيذ. يمكن أيضًا تمثيل الإطار باعتباره StackTraceElement (راجع Java API لـ StackTraceElement ). يمكنك قراءة المزيد عن الذاكرة المخصصة لكل موضوع هنا . إذا نظرنا إلى Java API وبحثنا عن كلمة Thread، فسنرى أن هناك فئة java.lang.Thread . هذه الفئة هي التي تمثل الدفق في Java، وهذا هو ما يتعين علينا العمل عليه. Thread'ом Java не испортишь: Часть I — потоки - 5

java.lang.Thread

يتم تمثيل مؤشر الترابط في Java كمثيل للفئة java.lang.Thread. من الجدير أن نفهم على الفور أن مثيلات فئة Thread في Java ليست سلاسل رسائل بحد ذاتها. هذا مجرد نوع من واجهة برمجة التطبيقات (API) للسلاسل ذات المستوى المنخفض التي تتم إدارتها بواسطة JVM ونظام التشغيل. عندما نقوم بتشغيل JVM باستخدام مشغل Java، فإنه يقوم بإنشاء سلسلة رسائل رئيسية باسم mainوالعديد من سلاسل رسائل الخدمة الأخرى. كما هو مذكور في JavaDoc لفئة Thread: When a Java Virtual Machine starts up, there is usually a single non-daemon thread هناك نوعان من المواضيع: الشياطين وغير الشياطين. خيوط الشيطان هي خيوط خلفية (خيوط الخدمة) تؤدي بعض الأعمال في الخلفية. هذا المصطلح المثير للاهتمام هو إشارة إلى "شيطان ماكسويل"، والذي يمكنك قراءة المزيد عنه في مقالة ويكيبيديا حول " الشياطين ". كما هو مذكور في الوثائق، يستمر JVM في تنفيذ البرنامج (العملية) حتى:
  • لم يتم استدعاء الأسلوب Runtime.exit
  • أكملت جميع المواضيع غير الشيطانية عملها (سواء بدون أخطاء أو مع طرح استثناءات)
ومن هنا تأتي التفاصيل المهمة: يمكن إنهاء سلاسل العمليات الشيطانية عند تنفيذ أي أمر. ولذلك، فإن سلامة البيانات الموجودة فيها ليست مضمونة. ولذلك، فإن الخيوط الخفية مناسبة لبعض مهام الخدمة. على سبيل المثال، يوجد في Java مؤشر ترابط مسؤول عن معالجة طرق الإنهاء أو سلاسل الرسائل المتعلقة بـ Garbage Collector (GC). ينتمي كل موضوع إلى مجموعة ما ( ThreadGroup ). ويمكن للمجموعات أن تدخل في بعضها البعض، وتشكل بعض التسلسل الهرمي أو الهيكل.
public static void main(String []args){
	Thread currentThread = Thread.currentThread();
	ThreadGroup threadGroup = currentThread.getThreadGroup();
	System.out.println("Thread: " + currentThread.getName());
	System.out.println("Thread Group: " + threadGroup.getName());
	System.out.println("Parent Group: " + threadGroup.getParent().getName());
}
تسمح لك المجموعات بتبسيط إدارة التدفقات وتتبعها. بالإضافة إلى المجموعات، المواضيع لديها معالج الاستثناء الخاص بها. لنلقي نظرة على مثال:
public static void main(String []args) {
	Thread th = Thread.currentThread();
	th.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
		@Override
		public void uncaughtException(Thread t, Throwable e) {
			System.out.println("An error occurred: " + e.getMessage());
		}
	});
    System.out.println(2/0);
}
سيؤدي القسمة على صفر إلى حدوث خطأ سيكتشفه المعالج. إذا لم تقم بتحديد المعالج بنفسك، فسيعمل تنفيذ المعالج الافتراضي، والذي سيعرض مكدس الأخطاء في StdError. يمكنك قراءة المزيد في المراجعة http://pro-java.ru/java-dlya-opytnyx/obrabotchik-neperexvachennyx-isklyuchenij-java/ ". بالإضافة إلى ذلك، فإن الموضوع له أولوية. يمكنك قراءة المزيد عن الأولويات في مقالة " أولوية مؤشر ترابط Java في تعدد مؤشرات الترابط ".

إنشاء موضوع

كما هو مذكور في الوثائق، لدينا طريقتان لإنشاء سلسلة رسائل. الأول هو إنشاء وريثك. على سبيل المثال:
public class HelloWorld{
    public static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("Hello, World!");
        }
    }

    public static void main(String []args){
        Thread thread = new MyThread();
        thread.start();
    }
}
كما ترون، يتم إطلاق المهمة في الطريقة run، ويتم تشغيل الخيط في الطريقة start. ولا ينبغي الخلط بينهما، لأن... إذا قمنا بتشغيل الطريقة runمباشرة، فلن يتم بدء أي موضوع جديد. إنها الطريقة startالتي تطلب من JVM إنشاء سلسلة رسائل جديدة. يعد خيار السليل من Thread سيئًا لأننا نقوم بتضمين Thread في التسلسل الهرمي للفئة. العيب الثاني هو أننا بدأنا في انتهاك مبدأ "المسؤولية الفردية" SOLID، لأنه يصبح فصلنا مسؤولاً في نفس الوقت عن إدارة سلسلة المحادثات وعن بعض المهام التي يجب تنفيذها في هذا الموضوع. ايهم صحيح؟ الجواب بنفس الطريقة runالتي نتجاوزها:
public void run() {
	if (target != null) {
		target.run();
	}
}
إليك targetبعض العناصر java.lang.Runnableالتي يمكننا تمريرها إلى Thread عند إنشاء مثيل للفئة. لذلك يمكننا القيام بذلك:
public class HelloWorld{
    public static void main(String []args){
        Runnable task = new Runnable() {
            public void run() {
                System.out.println("Hello, World!");
            }
        };
        Thread thread = new Thread(task);
        thread.start();
    }
}
إنها أيضًا Runnableواجهة وظيفية منذ Java 1.8. يتيح لك ذلك كتابة رمز المهمة للسلاسل بشكل أكثر جمالاً:
public static void main(String []args){
	Runnable task = () -> {
		System.out.println("Hello, World!");
	};
	Thread thread = new Thread(task);
	thread.start();
}

المجموع

لذا، آمل من هذه القصة أن يكون واضحًا ما هو الدفق وكيفية وجوده وما هي العمليات الأساسية التي يمكن إجراؤها به. في الجزء التالي ، من المفيد فهم كيفية تفاعل الخيوط مع بعضها البعض وما هي دورة حياتها. # فياتشيسلاف
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION