معرفی
Multithreading از همان ابتدا در جاوا ساخته شد. بنابراین، اجازه دهید به طور خلاصه به این چیزی که multithreading نام دارد نگاه کنیم.
ما درس رسمی از Oracle را به عنوان یک نقطه مرجع می گیریم: "
درس: برنامه "Hello World!
". ما کمی کد برنامه Hello World خود را به صورت زیر تغییر می دهیم:
class HelloWorldApp {
public static void main(String[] args) {
System.out.println("Hello, " + args[0]);
}
}
args
آرایه ای از پارامترهای ورودی است که هنگام شروع برنامه ارسال می شود. این کد را در فایلی با نامی که با نام کلاس مطابقت دارد و پسوند دارد ذخیره کنید
.java
. آن را با استفاده از ابزار
جاواک
کامپایل کنید :
javac HelloWorldApp.java
. سپس، کد خود را با پارامتری اجرا می کنیم، به عنوان مثال، "راجر":
java HelloWorldApp Roger
کد ما در حال حاضر یک نقص جدی دارد. اگر هیچ آرگومانی را ارسال نکنید (یعنی فقط "java HelloWorldApp" را اجرا کنید)، یک خطا دریافت می کنیم:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
at HelloWorldApp.main(HelloWorldApp.java:3)
یک استثنا (یعنی یک خطا) در موضوعی به نام "main" رخ داده است. بنابراین، جاوا دارای موضوعات است؟ این جایی است که سفر ما آغاز می شود.
جاوا و موضوعات
برای اینکه بفهمید thread چیست، باید نحوه شروع یک برنامه جاوا را بدانید. بیایید کد خود را به صورت زیر تغییر دهیم:
class HelloWorldApp {
public static void main(String[] args) {
while (true) {
}
}
}
حالا بیایید دوباره آن را با کامپایل کنیم
javac
. برای راحتی، کد جاوا خود را در یک پنجره جداگانه اجرا می کنیم. در ویندوز این کار را می توان به صورت زیر انجام داد:
start java HelloWorldApp
.
اکنون از ابزار jps
استفاده می کنیم تا ببینیم جاوا چه اطلاعاتی را می تواند به ما بگوید:
اولین عدد PID یا Process ID است. فرآیند چیست؟
A process is a combination of code and data sharing a common virtual address space.
با فرآیندها، برنامههای مختلف در حین اجرا از یکدیگر جدا میشوند: هر برنامهای بدون تداخل با برنامههای دیگر، از ناحیه خود در حافظه استفاده میکند. برای کسب اطلاعات بیشتر، خواندن این آموزش را توصیه می کنم:
Processes and Threads
. یک فرآیند نمی تواند بدون نخ وجود داشته باشد، بنابراین اگر یک فرآیند وجود داشته باشد، حداقل دارای یک رشته است. اما این چگونه در جاوا به وجود می آید؟ وقتی یک برنامه جاوا را راه اندازی می کنیم، اجرا با
main
متد شروع می شود. گویی در حال قدم گذاشتن در برنامه هستیم، بنابراین این
main
روش خاص را نقطه ورود می نامند. روش
main
باید همیشه "public static void" باشد تا ماشین مجازی جاوا (JVM) بتواند برنامه ما را اجرا کند. برای اطلاعات بیشتر،
چرا روش اصلی جاوا ثابت است؟
. به نظر می رسد که لانچر جاوا (java.exe یا javaw.exe) یک برنامه ساده C است: DLL های مختلفی را بارگیری می کند که در واقع JVM را تشکیل می دهند. راهانداز جاوا مجموعه خاصی از تماسهای رابط بومی جاوا (JNI) را ایجاد میکند. JNI مکانیزمی برای اتصال دنیای ماشین مجازی جاوا با دنیای ++C است. بنابراین، لانچر خود JVM نیست، بلکه مکانیزمی برای بارگذاری آن است. دستورات صحیح را برای راه اندازی JVM می داند. می داند که چگونه از تماس های JNI برای راه اندازی محیط لازم استفاده کند. راه اندازی این محیط شامل ایجاد thread اصلی است که البته به آن "main" می گویند. برای بهتر نشان دادن اینکه کدام رشته ها در یک فرآیند جاوا وجود دارند، از ابزار
jvisualvm
استفاده می کنیم که با JDK موجود است. با دانستن pid یک فرآیند، بلافاصله میتوانیم اطلاعات مربوط به آن فرآیند را ببینیم:
jvisualvm --openpid <process id>
جالب اینجاست که هر رشته دارای ناحیه جداگانهای در حافظه اختصاص داده شده به فرآیند است. این ساختار حافظه پشته نامیده می شود. یک پشته از قاب ها تشکیل شده است. یک فریم نشان دهنده فعال شدن یک متد (یک فراخوانی متد ناتمام) است. یک فریم همچنین می تواند به عنوان StackTraceElement نمایش داده شود (به API جاوا برای
StackTraceElement
مراجعه کنید ). می توانید اطلاعات بیشتری در مورد حافظه اختصاص داده شده به هر رشته در بحث اینجا بیابید: "
جاوا (JVM) چگونه پشته را برای هر رشته تخصیص می دهد
".
اگر به Java API
نگاه کنید و کلمه "Thread" را جستجو کنید، کلاس
java.lang.Thread
را خواهید یافت . این کلاسی است که یک thread را در جاوا نشان می دهد و ما باید با آن کار کنیم.
java.lang.thread
در جاوا، یک رشته با نمونه ای از
java.lang.Thread
کلاس نمایش داده می شود. باید فوراً متوجه شوید که نمونههای کلاس Thread خود رشتههای اجرایی نیستند. این فقط نوعی API برای رشته های سطح پایینی است که توسط JVM و سیستم عامل مدیریت می شوند. هنگامی که JVM را با استفاده از لانچر جاوا راه اندازی می کنیم، یک
main
رشته به نام "main" و چند رشته خانه داری دیگر ایجاد می کند. همانطور که در JavaDoc برای کلاس Thread بیان شد:
When a Java Virtual Machine starts up, there is usually a single non-daemon thread
. 2 نوع نخ وجود دارد: دیمون و غیر دیمون. thread های Daemon رشته های پس زمینه (خانه داری) هستند که کارهایی را در پس زمینه انجام می دهند. کلمه دیمون به دیو ماکسول اشاره دارد.
در این مقاله ویکی پدیا
می توانید اطلاعات بیشتری کسب کنید . همانطور که در مستندات ذکر شده است، JVM به اجرای برنامه (فرایند) تا زمانی که:
- متد Runtime.exit()
فراخوانی می شود
- همه رشتههای غیر دیمون کار خود را به پایان میرسانند (بدون خطا یا با استثناء پرتاب شده)
یک جزئیات مهم از این نتیجه می شود: رشته های شبح را می توان در هر نقطه ای خاتمه داد. در نتیجه، هیچ تضمینی در مورد یکپارچگی داده های آنها وجود ندارد. بر این اساس، نخ های دیمون برای کارهای خاصی از خانه داری مناسب هستند. به عنوان مثال، جاوا دارای یک رشته است که وظیفه پردازش
finalize()
فراخوانی های متد را بر عهده دارد، یعنی رشته هایی که با زباله گرد (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());
}
گروه ها به مدیریت رشته نظم می دهند. علاوه بر گروه ها، thread ها کنترل کننده استثنای خود را دارند. به یک مثال نگاه کنید:
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);
}
تقسیم بر صفر باعث ایجاد خطایی می شود که توسط کنترل کننده تشخیص داده می شود. اگر کنترل کننده خود را مشخص نکنید، JVM کنترل کننده پیش فرض را فراخوانی می کند که ردیابی پشته استثنا را به StdError خروجی می دهد. هر تاپیک نیز دارای اولویت است. در این مقاله میتوانید درباره اولویتها بیشتر بخوانید:
اولویت موضوع جاوا در Multithreading
.
ایجاد یک موضوع
همانطور که در مستندات گفته شد، ما 2 راه برای ایجاد یک موضوع داریم. راه اول این است که زیر کلاس خود را ایجاد کنید. مثلا:
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()
متد اتفاق می افتد، اما خود thread در
start()
متد شروع می شود. این روش ها را اشتباه نگیرید: اگر
un()
مستقیماً متد r را فراخوانی کنیم، هیچ موضوع جدیدی شروع نمی شود. این
start()
روشی است که از JVM می خواهد یک رشته جدید ایجاد کند. این گزینه که در آن Thread را به ارث می بریم از این نظر بد است که ما Thread را در سلسله مراتب کلاس خود قرار می دهیم. دومین اشکال این است که ما شروع به نقض اصل "مسئولیت واحد" کرده ایم. یعنی کلاس ما به طور همزمان مسئولیت کنترل thread و انجام برخی کارها در این Thread را بر عهده دارد. راه درست چیست؟ پاسخ در همان
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
همچنین از جاوا 1.8 یک رابط کاربردی بوده است. این امکان نوشتن کدهای زیباتر برای کار یک رشته را فراهم می کند:
public static void main(String[] args) {
Runnable task = () -> {
System.out.println("Hello, World!");
};
Thread thread = new Thread(task);
thread.start();
}
نتیجه
امیدوارم این بحث روشن کند که نخ چیست، نخ ها چگونه به وجود می آیند، و چه عملیات اساسی را می توان با نخ ها انجام داد. در
قسمت بعدی
، ما سعی خواهیم کرد بفهمیم که چگونه رشته ها با یکدیگر تعامل دارند و چرخه عمر نخ را بررسی می کنیم.
بهتر با هم: جاوا و کلاس Thread. بخش دوم - همگام سازی
بهتر با هم: کلاس جاوا و Thread. بخش سوم - تعامل
بهتر با هم: کلاس جاوا و Thread. بخش چهارم - Callable، Future، و دوستان
بهتر با هم: Java and the Thread کلاس. قسمت پنجم - Executor، ThreadPool، Fork/Join
Better together: Java و کلاس Thread. قسمت ششم - آتش دور شوید!
GO TO FULL VERSION