تبلیغات

کلمه کلیدی volatile در جاوا

کلمه کلیدی volatile در جاوا

نگاهی کلی قبل از شروع درس: این کلمه‌ی کلیدی در مبحث thread مطرح می‌شود. وقتی چند thread با یک متغیر (مثلا یک شی) عادی کار می‌کنند، ممکن است آن را cache کنند. یعنی هر thread ای که این متغیر را cache کند، یک نسخه از آن گرفته و با آن کار می‌کند (تغییرات اعمالی بر روی آن توسط یک thread، به سایر thread ها منعکس نمی‌شود)

اگر آن متغییر با کلمه‌ی کلیدی volatile تعریف شود، تنها یک نسخه از آن متغیر وجود دارد و تغییراتی که یک thread بر روی آن انجام می‌دهد، بلافاصله در thread های دیگر منعکس می‌شود.


هنگامی که در مصاحبه های کاری و استخدامی مرتبط با جاوا شرکت می کنید ممکن است شخص مصاحبه گر سوالات مختلفی را از شما بپرسد که معمولاً این سوالات به منظور تعیین سطح علمی یا توانایی شما در برنامه نویسی نیستند و بیشتر قصد گیج کردن ، پایین نشون دادن سطح معملومات شما و خود شرکت بزرگ پنداری (بزرگ نشون دادن شرکت خودشون) دارند. متاسفانه در موارد بسیاری نیز مصاحبه گر خود را خدای جاوا (یا چیز دیگر) می پندارد (بعضاً نیز مصاحبه گر خود به پاسخ سوالات خود واقف نیست) ، به هر جهت ، یکی از مواردی که در سوالات مصاحبه های کاری مطرح می شود پرسش در باب کلمات کلیدی جاواست. volatile از جمله کلمات کلیدی است که شاید در مورد آن نشنیده باشید یا کمتر شنیده باشید و این امکان وجود دارد که در مصاحبه ای بخواهند از این کلمه به عنوان برگ برنده بر علیه شما استفاده کنند. در این آموزش با کلمه کلیدی volatile در جاوا آشنا می شویم.

 Volatile هم به این صورت همراه متغیر تعریف می شه

نکته: استفاده از Volatile در برنامه هایی که تنها از یه Thread استفاده می شه کاربردی نداره…

تعریف Volatile
عمل خواندن و نوشتن یک متغیر volatile به صورت atomic و در حافظه اصلی (Main Memory) انجام می شود. (از کش استفاده نمی شود و هر تغییری مستقیماً در حافظه ثبت می شود لذا استفاده از یک متغیر volatile کارایی برنامه را نسبت به استفاده از متغیر معمولی کاهش می دهد ).

عمل atomic
عملی است که قابل تجزیه به اعمال کوچک تر نباشد ، عمل atomic یا به طور کامل انجام می شود یا انجام نمی شود و هیچ اتفاقی نمی تواند در وسط اجرای یک عمل atomic صورت پذیرد.

  • عمل خواندن و نوشتن متغیر های مرجع (reference variable) و بیشتر داده های نوع اولیه (به غیر از double و long) اتمیک به حساب می آید.
  • عمل خواندن و نوشتن متغیر های از نوع volatile (حتی متغیر های از نوع double و long) اتمیک به حساب می آید.

نکته: متغیر های double و long از دو نوع داده کوچکتر (int و float ) در سطح JVM استفاده می کنند و تغییرات بر روی آن ها در حقیقت تغییر بر روی دو متغیر به حساب می آید لذا عمل خواندن و نوشتن نوع داده های long و double اتمیک نخواهد بود مگر اینکه از نوع volatile باشند.


volatile بهینه نمی شود
optimizer برای بهبود کد ها می توانند کد ها را جا به جا کند و در جاهایی روش های مقدار دهی را نیز تغییر دهد تا کد نهایی بهینه تری ایجاد شود ، متغیر هایی که از نوع volatile تعریف شوند توسط optimizer دستکاری نمی شوند.

volaticle نمی تواند final باشد.
یک متغیر نمی تواند همزمان هم volatile و هم final باشد و عباراتی مانند عبارت زیر در زمان کامپایل با خطا مواجه خواهد شد.

volatile در محیط های دارای چند Thread استفاده می شود.
همانطور که قبلاً هم اشاره کردم volatile در محیط های Multi Threading استفاده می شود و هنگامی از آن استفاده می کنیم که یک متغیر مشترک (shared variable) بین Thread ها داریم و می خواهیم عمل نوشتن و خواندن آن به صورت اتمیک صورت پذیرد). اساساً استفاده از volatile در برنامه های تک ریسمانی بیهوده بوده و باعت کاهش کارایی برنامه (به دلیل عدم استفاده از کش) می شود.

volatile یا synchronized
ممکن است این سوال برای شما پیش بیاید که synchronized نیز برای اتمیک کردن بلوک از کد می تواند استفاده شود پس چرا باید از volatile استفاده کنیم؟

  1. اگر چه volatile و synchronized از لحاظ مفهوم بسیار به هم شبیه هستند ولی مفاهیم متفاوتی هستند و در ادامه در قالب یک مثال خواهیم دید که گاهی می توانند متفاوت باشند (گاهی نیز ممکن است یکسان عمل کنند).
  2.  volatile از synchronized بهینه تر است ، در مواقعی که بتوانیم یک مسئله خاص را هم با volatile و هم با synchronized حل کنیم استفاده از volatile منجر به تولید کد کارا تری می شود.
  3.  volatile هنگامی استفاده می شود که فقط یک ریسمان نویسنده (ریسمانی که روی متغیر مشترک از نوع volatile می نویسد) و چندین ریسمان خواننده داشته باشیم ولی synchronized  قادر به حل مسائل گسترده تری است.
  4.  volatile فقط خواندن و نوشتن را اتمیک می کند و نه هر نوع تغییری را ، در صورتی که با استفاده از synchronized قادر هستیم بلوکی از کد را به صورت اتمیک ایجاد کنیم.

مثال

اگرچه دو متد nonAtomic  و atomic عملیات مشابهی انجام می دهند ولی متد nonAtomic می تواند در وسط کار توسط ریسمان دیگری متوقف شود ولی متد atomic هر بار تنها توسط یک ریسمان قابل اجرا خواهد بود.

نه volatile و نه synchronized
معمولاً مسائل مرتبط با Multi Threading یا پیچیده نیستند یا خیلی پیچیده هستند یعنی در برنامه های چند ریسمانی یا هیچ چیز برای شما اهمیت ندارد یا همه چیز زیادی اهمیت دارند. در عمل کمتر اتفاق می افتد که استفاده از volatile یا synchronized قادر به حل مسائل ما باشند و در بیشتر مسائل مهم چند ریسمانی باید از مفاهیم پیچیده تر مانند semaphore استفاده کنید. خوشبختانه java.util.concurrent و java.util.concurrent.lock و java.util.concurrent.atomic ابزار های بسیار مفیدی (مانند Semaphoe ) را در اختیار ما قرار می دهند که می توانیم از آن ها برای حل مسائل مختلف چند ریسمانی استفاده کنیم.

مثال
اگرچه درک volatile و کاربرد آن چندان شفاف نیست و ممکن است در نگاه اول کمی گیج کننده باشد (در عمل نیز احتمال اینکه بخواهید در آینده از آن استفاده کنید بسیار بسیار کم خواهد بود) ولی سعی می کنم در طی مثال مشهوری از مستندات اوراکل کاربرد آن را توضیح دهم.

به مثال زیر اهمیت ترتیب را نشان می دهد، می خواهیم همیشه j بعد از i به روز رسانی شود.

به کلاس زیر دقت کنید

فرض کنید یک Thread نویسنده به صورت پیوسته متد one را اجرا می کند (تا زمانی که مقادیر i و j در int جا شوند) و Thread (یا Threadهای) دیگری به صورت پیوسته متد two را اجرا می کند. چون هیچ گونه شرطی در مورد به روز رسانی متغیر های i و j توسط Thread نویسنده اعمال نشده است (نه volatile و نه synchronized) لذا ممکن است در مواقعی j زودتر (خارج از ترتیب – out of order) از i آپدیت شود و Thread خواننده در مواقعی مقادیری را چاپ کند که در آن مقدار j از i بیشتر است.

حل با synchronized

در اینجا متد های one و two هر دو سنکرون سازی شده اند لذا هیچ گاه این دو متد همزمان اجرا نمی شوند ، به علاوه هنگامی که وارد متد one می شویم تا زمانی که مقادیر i و j آپدیت نشوند از متد one خارج نمی شویم پس هنگامی که متد two قصد خواندن مقادیر i و j را داشته باشید مطئمناً i و j هر دو به روز شده اند و این یعنی هیچ گاه متد two نمی تواند مقادیری را چاپ کند که در آن j بزرگتر از i باشد به علاوه به دلیل اتمیک شدن متد one همیشه j بعد از i به روز رسانی می شوند (ترتیب حفظ می شود).

حل با volatile
به راه حل مبتنی بر volatile دقت کنید ، ممکن است کمی در ابتدا گیج کننده باشد

چون هر دو متغیر i و j از نوع volatile هستند هیچگاه j زود تر از i به روز رسانی نمی شود در حقیقت همیشه ابتدا i باید به روز شود سپس j به روز می شود.(پس ترتیب در نوشتن حفظ می شود). با این وجود ممکن است متد two مقادیری را چاپ کند که در آن مقادیر j از i بیشتر باشند. چرا چنین چیزی ممکن است؟ چون متد های one و two اتمیک نیستند و ممکن است سناریوی زیر اتفاق بیفتد :

  • متد one متغیر i را به روز رسانی می کند.
  • کنترل سی پی یو از متد one گرفته شده به متد two داده می شود (چون دو ریسمان داریم)
  • متد two مقدار i را چاپ می کند ولی قبل از اینکه قادر به چاپ j باشد کنترل از آن گرفته می شود و به Thread نویسنده داده می شود.
  • ریسمان نویسنده در فرصتی مناسبت چندین بار متد one را فراخوانی می کند (i و j چندین بار به روز رسانی می شوند)
  • کنترل به ریسمان خواننده داده می شود و ادامه متد two (یعنی چاپ j) انجام می شود و در این حالت مقدار j ای بزرگتر از i چاپ می شود.

مثال فوق نشان می دهد که ریسمان نویسنده ترتیب را حفظ می کند (هیچ گاه j زود تر از i به روز نمی شود) ولی ریسمان خواننده ممکن است به درستی این موضوع را نشان ندهد.

اگر فقط ترتیب در نوشتن برای ما مهم باشد راه حل volatile در مثال بالا کافی است. اگر ترتیب هم در نوشتن و هم در خواندن مهم باشد  روش استفاده از volatile پاسخگو نیست و باید از synchronized یا روش های قوی تر استفاده کنیم.

مسائل Multi Threading در واقعیت خیلی خیلی از مثال فوق پیچیده تر هستند و می توانید در درس هایی مانند “طراحی سیستم عامل” یا “طراحی شبکه” با مسائل مختلفی از قبیل مسائل IPC آشنا شوید تا بهتر پیچیدگی ها موجود در مسائل Multi Threading را درک کنید.

تبلیغات
5 نظر
کانال تلگرام فول کده
تبلیغات

درباره نویسنده

هادی اکبرزاده

[ مدیر فول کده ]

علاقه‌مند به اشتراک گذاری اطلاعات در هر زمینه‌ای / برنامه‌نویس / مدیر فول کده

پاسخ دهید

نظرات ثبت شده 5 دیدگاه