سلام در این سری از آموزش ها قصد داریم مبحث برنامه نویسی چند نخی در جاوا (Multithreading) را از پایه تا سطح حرفه ای بررسی کنیم.
توجه: مخاطبین این آموزش توجه داشته باشند که برای فهم کلیه ی مطالب این بخش باید آشنایی کامل با مباحث شی گرایی (OOP) ، واسط (Interface) و... داشته باشند. بنابراین جهت مطالعه این پیش نیاز لطفا دوره زیر را که به صورت رایگان می باشد مطالعه کنید:
منظور از برنامه نویسی چند نخی (Multithreading) چیست؟
تا اینحا تمامی برنامه هایی که نوشتید به صورت ترتیبی یا Sequential بوده یعنی کدها به ترتیب یکی پس از دیگری اجرا می شدند؛
در برنامه نویسی چند نخی (Multithreading) قابلیت پردازش چندین عمل به صورت کاملا همزمان به برنامه داده می شود.
اما چگونه برنامه ای بنویسیم که به صورت همزمان تعدادی کد را بدون این که تداخلی در پردازش پیش آید، اجرا کند.
سوال مهمی که اینجا پیش می آید این است که اصلا برنامه نویسی چند نخی (Multithreading) چه کارایی و اهمیتی دارد یا چرا باید چندین عمل به صورت همزمان اجرا شوند؟
حالتی را در نظر بگیرید که در حال کپی فایلی در کامپیوتر خود هستید و می خواهید همزمان موسیقی گوش کنید؛ این کار به راحتی قابل انجام است چون برنامه نویسان سیستم عامل به بهترین شکل از روش چند نخی در کد نویسی آن استفاده کرده اند.
یک آنتی ویروس را در نظر بگیرید همزمان می تواند سیستم شما را اسکن کند و در صورت پیدا شدن ویروس به شما اطلاع دهد بدون این که تاخیری در ادامه ی اسکن انجام شود، این ها مثال هایی از برنامه نویسی چند نخی و اهمیت استفاده از آن بود؛ شما هر روزه از برنامه هایی استفاده می کنید که در آن از برنامه نویسی چند نخی استفاده شده است.
پس در ادامه قوانین، مشکلات احتمالی، چرخه ی حیات یک نخ، امکانات جدید جاوا برای برنامه نویسی چند نخی (Multithreading)، نوع داده های Thread-safe و ... را بررسی می کنیم .
1-مفهوم چند پردازشی ( Multi-Processing)
یعنی سیستم عامل بتواند چند برنامه را همزمان اجرا کند. مثلا اجرای همزمان Netbeans و Chrome
2-مفهوم چند نخی (Multithreading)
یعنی یک برنامه بتواند چند بخش را همزمان اجرا کند، که هر جریان توسط یک نخ اجرایی، کنترل می شود. به طوری که همزمان با اجرای یک متد، متدی دیگر هم در حال اجرا باشد. به اجرای همزمانی چند بخش، همروندی یا Concurrency گویند.
رفع یک سوء تفاهم
مفهوم اجرای موازی (Parallel) یعنی دو دستور واقعا هزمان با هم در حال اجرا هستند. مانند یک پردازنده ی چند هسته ای، که هر هسته می تواند متدی را اجرا کند. ولی مفهوم اجرای همروند یعنی ظاهرا چند بخش همزمان با هم در حال اجرا و پیشرفت باشند ولی لزوما به صورت موازی اجرا نمی شوند، حتی ممکن است در هر لحظه یکی از کار ها در حال انجام باشد.
عکس زیر به خوبی این مطلب را نشان می دهد.
وقتی یک برنامه ی جاوا را اجرا می کنید، در واقع یک نخ به صورت خودکار اجرا می شود که وظیفه ی آن اجرای متد Main است، برنامه نویس هم می تواند به صورت دستی نخ هایی تولید و اجرا کند تا وظایفی را انجام دهد و در پایان کار نابود شوند. (به اصطلاح نخ می میرد)
نخ های ایجاد شده به صورت Concurrency اجرا می شوند و شاید به صورت موازی هم قابل اجرا باشند، در این مورد برنامه نویس کنترلی بر نوع و ترتیب اجرای نخ ها ندارد و کاملا وابسته به امکانات سخت افزاری و برنامه های موجود در حال اجرا است.
خب توضیحات اولیه کافی بوده و ممکن است احساس کنید کمی گیج شده اید. هیچ اشکالی نداره چون مبحث Thread در همه ی زبان ها مبحثی پیچیده بوده و فقط با تمرین و مثال قابل درک است، پس به سراغ کدنویسی رفته و مفاهیم اصلی را قدم به قدم همراه با مثال بررسی می کنیم.
در برنامه نویسی جاوا دو راه کلی برای ایجاد و تعریف رفتار یک نخ وجود دارد که در هر دور روش باید کلاس جدید ایجاد کنیم.
در هر دو روش باید متد Run در کلاس جدید Override شود، در واقع دستورات و وظیفه ی آن نخ در این متد تعریف می شود.
روش اول :Java.lang.Thread
کلاس جدیدی ایجاد کنید که کلاس Thread را extends کند و متد Run را درون آن کلاس Override کنید.
class MyThread extends Thread{ @Override public void run() { System.out.println("test 2"); System.out.println("test 3"); } }
وظیفه ای که در اینجا برای این نخ در نظر گرفته شده چاپ دو جمله test 2 و test 3 است که به این صورت انجام می شود.
با استفاده از شی ساخته شده از کلاس قبل در متد Main و فراخوانی متد Start توسط شی کلاس، نخ را اجرا کنید.
public class Main{ public static void main(String[] args) { System.out.println("test 1"); MyThrrad myThread= new MyThread(); myThread.start(); System.out.println("test 4"); } }
نخ اجرا کننده ی متد main ابتدا test 1 را در خروجی چاپ می کند بعد از ایجاد شی از کلاس MyThread، روند اجرای برنامه به دو قسمت تقسیم می شود و نخ کلاس MyThread به صورت همروند با نخ کلاس Main اجرا می شود.
نکته ی بسیار مهمی که در اینجا باید به آن توجه کرد این است که حتما در ابتدا test 1 چاپ می شود، ولی هیچ تضمینی وجود ندارد که بعد از آن test 2 و test 3 و در آخر test 4 چاپ شود.
خروجی من به شکل زیر است و ممکن است برای شما متفاوت باشد، پس در اینجا نخ Main (اصلی) وظیفه ی چاپ test 1 و test 4 و نخ کلاس MyThread وظیفه ی چاپ test 2 و test 3 را بر عده دارد.
روش دوم:Java.lang.Runnable
در کلاس جدیدی که ایجاد کرده اید واسط Runnable را پیاده سازی کنید و متد Run آن را Override کنید.
class MyThread implements Runnable{ @Override public void run() { System.out.println("test 2"); System.out.println("test 3"); } }
در این روش علاوه بر ایجاد شی از این کلاس در متد Main، یک شی از کلاس Thread نیز ایجاد کنید و به عنوان پارامتر ورودی سازنده ی کلاس Thread، شی کلاسی را در آن وارد نمایید که واسط Runnable را پیاده سازی کرده و با استفاده از شی کلاس Thread، متد Start را فراخوانی کنید تا نخ مورد نظر ایجاد شده و وظیفه ی خود را انجام دهد.
public class Main{ public static void main(String[] args) throws InterruptedException { System.out.println("test 1"); MyThrrad myThread = new MyThread(); Thread t = new Thread(myThread ); t.start(); System.out.println("test 4"); } }
خروجی و تمامی قوانین روش اول برای این مورد نیز صادق است، فقط روش ایجاد نخ متفاوت بوده و در کل مثال هایی که در این سری از آموزش ها مورد بررسی قرار می گیرند فارغ از این که با چه روشی نخ را ایجاد کنیم، کاملا دارای قوانین یکسان بوده و کوچکترین تفاوتی ندارند.
سوال: شاید برای شما هم این سوال پش آمده که چرا دو روش ایجاد نخ در جاوا وجود دارد، کدام روش بهتر و معمول تر است؟
پاسخ: خب کاملا مشخص است که روش اول ( extends) روش ساده تری می باشد ولی با توجه به این نکته که جاوا از ارث بری چند گانه پشتیبانی نمی کند این مسئله می تواند مشکل ساز باشد. پس در مواردی که کلاس نیاز دارد از کلاس دیگری ارث بری کند این روش، روشی مفید نیست؛ اما در روش دوم (implements) کمی پیچیده تر بوده ولی چون با استفاده از یک واسط نخ اجرا می شود و جاوا قابلیت پیاده سازی چندین واسط را به صورت همزمان دارا می باشد، پس از آزادی عمل بیشتری برای عمل ارث بری برخوردار است.
و اما یک سوال دیگر، چرا در کلاس جدید متد Run پیاده سازی می کنیم و دستورات را در آن می نویسیم ولی در هنگام اجرای نخ متد Start فراخوانی می شود؟
پاسخ: متد Start یکی از متد های کلاس Thread است، که فعالت های سطح پایین را اجرا می کند مثلا ایجاد نخ؛ در نخ جدیدی که ایجاد کرده خودش متد Run را فراخوانی و دستورات آن را اجرا می کند.
خب بخش اول آموزش به پایان رسید. در قسمت بعدی آموزش ابتدا کلاس بسیار مهم Thread و متدهای مهم آن را مرور می کنیم و با کاربرد آن ها آشنا می شویم و سپس نگاهی دقیق تر به چرخه ی حیات یک نخ از تولد تا مرگ می اندازیم تا مبحث Thread را به صورت مفهومی یاد بگیرید.
موفق باشید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.