CodeGym /وبلاگ جاوا /Random-FA /مدیریت موضوعات کلمه کلیدی فرار و متد yield().
John Squirrels
مرحله
San Francisco

مدیریت موضوعات کلمه کلیدی فرار و متد yield().

در گروه منتشر شد
سلام! ما مطالعه خود را در مورد multithreading ادامه می دهیم. volatileامروز با کلمه کلیدی و روش آن آشنا می شویم yield(). بیا شیرجه بزنیم :)

کلمه کلیدی فرار

هنگام ایجاد برنامه های چند رشته ای، می توانیم با دو مشکل جدی مواجه شویم. اول، هنگامی که یک برنامه چند رشته ای در حال اجرا است، رشته های مختلف می توانند مقادیر متغیرها را در حافظه پنهان ذخیره کنند (ما قبلاً در این مورد در درس با عنوان 'استفاده از فرار' صحبت کردیم ). شما می توانید وضعیتی داشته باشید که یک رشته مقدار یک متغیر را تغییر می دهد، اما رشته دوم این تغییر را نمی بیند، زیرا با کپی حافظه پنهان متغیر خود کار می کند. به طور طبیعی، عواقب آن می تواند جدی باشد. فرض کنید این فقط یک متغیر قدیمی نیست، بلکه موجودی حساب بانکی شما است که ناگهان شروع به بالا و پایین پریدن می کند :) به نظر جالب نمی رسد، درست است؟ دوم، در جاوا، عملیات خواندن و نوشتن همه انواع اولیه، به جز longو double، اتمی هستند. خوب، برای مثال، اگر مقدار یک intمتغیر را در یک رشته تغییر دهید، و در یک رشته دیگر، مقدار متغیر را بخوانید، یا مقدار قبلی آن را دریافت می کنید یا مقدار جدید، یعنی مقداری که از تغییر حاصل شده است. در موضوع 1. هیچ "مقادیر میانی" وجود ندارد. longبا این حال، این با s و doubles کار نمی کند . چرا؟ به دلیل پشتیبانی از پلتفرم های مختلف. به یاد داشته باشید که در سطوح ابتدایی گفتیم که اصل راهنمای جاوا این است که "یک بار بنویس، هر جا اجرا شود"؟ این به معنای پشتیبانی بین پلتفرمی است. به عبارت دیگر، یک برنامه جاوا بر روی انواع پلتفرم های مختلف اجرا می شود. به عنوان مثال، در سیستم عامل های ویندوز، نسخه های مختلف لینوکس یا MacOS. بدون هیچ مشکلی روی همه آنها اجرا خواهد شد. وزن آن 64 بیت است longو double«سنگین‌ترین» اولیه‌های جاوا هستند. و برخی از سیستم عامل های 32 بیتی به سادگی خواندن و نوشتن اتمی متغیرهای 64 بیتی را پیاده سازی نمی کنند. چنین متغیرهایی در دو عملیات خوانده و نوشته می شوند. ابتدا 32 بیت اول روی متغیر نوشته می شود و سپس 32 بیت دیگر نوشته می شود. در نتیجه ممکن است مشکلی پیش بیاید. یک رشته مقداری 64 بیتی برای یک Xمتغیر می نویسد و این کار را در دو عملیات انجام می دهد. در همان زمان، یک رشته دوم سعی می کند مقدار متغیر را بخواند و این کار را در بین این دو عملیات انجام می دهد - زمانی که 32 بیت اول نوشته شده است، اما 32 بیت دوم نوشته نشده است. در نتیجه یک مقدار متوسط ​​و نادرست می خواند و ما باگ داریم. به عنوان مثال، اگر در چنین پلتفرمی بخواهیم عدد 9223372036854775809 را روی یک متغیر بنویسیم ، 64 بیت را اشغال می کند. به شکل باینری ، به نظر می رسد: 100000000000000000000000000000000000000000000000000000000001 اولین موضوع شروع به نوشتن شماره به متغیر می کند. در ابتدا 32 بیت اول (100000000000000000000000000000000000000000000000000000000000000000000000000000000000000) و سپس 32 بیت دوم (00000000000000000000000000000000001) و رشته دوم می تواند بین این عملیات فرو رود، با خواندن مقدار میانی متغیر (1000000000000000000000000000000000000000000000000000000000) که 32 بیت اولی است که قبلاً نوشته شده است. در سیستم اعشاری این عدد 2,147,483,648 است. به عبارت دیگر ما فقط می خواستیم عدد 9223372036854775809 را روی یک متغیر بنویسیم، اما به دلیل اتمی نبودن این عملیات در برخی از پلتفرم ها، عدد شیطانی 2,147,483,648 را داریم که از ناکجاآباد بیرون آمده و اثر نامعلومی خواهد داشت. برنامه رشته دوم به سادگی مقدار متغیر را قبل از پایان نوشتن می خواند، یعنی نخ 32 بیت اول را دید، اما 32 بیت دوم را نه. البته این مشکلات دیروز به وجود نیامد. جاوا آنها را با یک کلمه کلیدی حل می کند: volatile. volatileاگر از کلمه کلیدی هنگام اعلان یک متغیر در برنامه خود استفاده کنیم …
public class Main {

   public volatile long x = 2222222222222222222L;

   public static void main(String[] args) {

   }
}
…این به آن معنا است:
  1. همیشه به صورت اتمی خوانده و نوشته خواهد شد. حتی اگر 64 بیتی doubleیا long.
  2. ماشین جاوا آن را کش نمی کند. بنابراین موقعیتی نخواهید داشت که 10 رشته با نسخه های محلی خود کار کنند.
بنابراین، دو مشکل بسیار جدی تنها با یک کلمه حل می شود :)

متد yield().

ما قبلاً بسیاری از Threadمتدهای کلاس را بررسی کرده ایم، اما روش مهمی وجود دارد که برای شما جدید خواهد بود. این yield()روش است . و دقیقاً همان کاری را انجام می دهد که از نامش پیداست! مدیریت موضوعات  کلمه کلیدی فرار و متد yield() - 2وقتی متد را روی یک رشته صدا می‌زنیم yield، در واقع با رشته‌های دیگر صحبت می‌کند: «هی بچه‌ها. من عجله خاصی برای رفتن به جایی ندارم، بنابراین اگر برای هر یک از شما مهم است که زمان پردازنده را دریافت کنید، آن را بگیرید - من می توانم صبر کنم. در اینجا یک مثال ساده از نحوه کار این است:
public class ThreadExample extends Thread {

   public ThreadExample() {
       this.start();
   }

   public void run() {

       System.out.println(Thread.currentThread().getName() + " yields its place to others");
       Thread.yield();
       System.out.println(Thread.currentThread().getName() + " has finished executing.");
   }

   public static void main(String[] args) {
       new ThreadExample();
       new ThreadExample();
       new ThreadExample();
   }
}
ما به صورت متوالی سه موضوع را ایجاد و شروع می کنیم: Thread-0، Thread-1، و Thread-2. Thread-0ابتدا شروع می شود و بلافاصله به دیگران تسلیم می شود. سپس Thread-1شروع می شود و همچنین بازده می دهد. سپس Thread-2شروع می شود که نتیجه می دهد. ما هیچ رشته‌ای نداریم، و پس از اینکه Thread-2آخرین جایگاهش را نشان داد، زمان‌بندی رشته می‌گوید: «هوم، رشته‌های جدیدی دیگر وجود ندارد. چه کسی را در صف داریم؟ چه کسی جای آن را قبلا تسلیم کرد Thread-2؟ به نظر می رسد که بود Thread-1. خوب، این بدان معناست که ما اجازه می دهیم اجرا شود. Thread-1کار خود را کامل می کند و سپس زمانبندی رشته به هماهنگی خود ادامه می دهد: "بسیار، Thread-1تمام شد. آیا ما شخص دیگری در صف داریم؟ Thread-0 در صف قرار دارد: درست قبل از Thread-1. اکنون نوبت خود را می‌گیرد و به اتمام می‌رسد. سپس زمان‌بند هماهنگ کردن رشته‌ها را به پایان می‌رساند: «بسیار خوب، Thread-2شما به رشته‌های دیگر تسلیم شدید و اکنون همه آنها تمام شده‌اند. تو آخرین نفری بودی که تسلیم شدی، پس حالا نوبت توست. سپس Thread-2برای تکمیل اجرا می شود. خروجی کنسول به این صورت خواهد بود: Thread-0 جای خود را به دیگران می دهد Thread-1 جای خود را به دیگران می دهد Thread-2 جای خود را به دیگران می دهد. Thread-1 اجرای خود را به پایان رسانده است. اجرای Thread-0 به پایان رسیده است. اجرای Thread-2 به پایان رسیده است. البته، زمان‌بندی رشته ممکن است رشته‌ها را به ترتیب دیگری شروع کند (مثلاً 2-1-0 به جای 0-1-2)، اما اصل یکسان است.

اتفاق می افتد-قبل از قوانین

آخرین چیزی که امروز به آن خواهیم پرداخت مفهوم " پیش از این اتفاق می افتد " است. همانطور که قبلاً می دانید، در جاوا زمانبندی thread بخش عمده ای از کار مربوط به تخصیص زمان و منابع به رشته ها را برای انجام وظایف خود انجام می دهد. همچنین بارها مشاهده کرده اید که چگونه رشته ها به ترتیب تصادفی اجرا می شوند که معمولاً پیش بینی آن غیرممکن است. و به طور کلی، پس از برنامه نویسی "متوالی" که قبلا انجام دادیم، برنامه نویسی چند رشته ای چیزی تصادفی به نظر می رسد. شما قبلاً به این باور رسیده اید که می توانید از روش های مختلفی برای کنترل جریان یک برنامه چند رشته ای استفاده کنید. اما multithreading در جاوا یک رکن دیگر دارد - قانون 4 " پیش از وقوع ". درک این قوانین بسیار ساده است. تصور کنید که ما دو رشته داریم - Aو B. هر یک از این رشته ها می توانند عملیات 1و 2. در هر قانون، وقتی می گوییم " A اتفاق می افتد قبل از B "، منظور ما این است که تمام تغییرات ایجاد شده توسط thread Aقبل از عملیات 1و تغییرات حاصل از این عملیات Bهنگام 2انجام عملیات و پس از آن برای thread قابل مشاهده است. هر قانون تضمین می کند که وقتی یک برنامه چند رشته ای می نویسید، 100٪ مواقع رویدادهای خاصی قبل از دیگران اتفاق می افتد و در زمان عملیات 2thread Bهمیشه از تغییراتی که thread Aدر طول عملیات ایجاد کرده است آگاه خواهد بود 1. آنها را مرور کنیم.

قانون 1.

انتشار یک mutex قبل از اینکه همان مانیتور توسط نخ دیگری بدست آید اتفاق می افتد. فکر کنم اینجا همه چیزو میفهمی اگر mutex یک شی یا یک کلاس توسط یک رشته به دست بیاید. برای مثال، توسط نخ A، رشته دیگر (thread B) نمی تواند آن را در همان زمان بدست آورد. باید صبر کرد تا mutex آزاد شود.

قانون 2.

روش قبلا اتفاق میThread.start() افتد . باز هم اینجا هیچ چیز سختی نیست. از قبل می دانید که برای شروع اجرای کد داخل متد، باید متد موجود در thread را فراخوانی کنید. به طور مشخص، روش شروع، نه خود روش! این قانون تضمین می کند که مقادیر همه متغیرهای تنظیم شده قبل از فراخوانی در داخل متد پس از شروع قابل مشاهده خواهد بود. Thread.run()run()start()run()Thread.start()run()

قانون 3.

پایان متد run()قبل از بازگشت از join()متد اتفاق می افتد. بیایید به دو موضوع خود برگردیم: Aو B. ما join()متد را فراخوانی می کنیم تا thread تضمین شود که قبل از اینکه کار خود را انجام دهد Bمنتظر تکمیل نخ بماند . Aاین بدان معناست که متد شیء A run()تا انتها اجرا می شود. و تمام تغییرات داده‌ها که در run()روش thread اتفاق می‌افتد A، صد در صد تضمین می‌شود که Bپس از اتمام کار در رشته قابل مشاهده هستند و منتظر می‌مانند تا Thread Aکار خود را تمام کند تا بتواند کار خود را شروع کند.

قانون 4.

نوشتن روی یک volatileمتغیر قبل از خواندن از همان متغیر اتفاق می افتد. وقتی از کلمه کلیدی استفاده می کنیم volatile، در واقع همیشه مقدار فعلی را دریافت می کنیم. حتی با یک longیا double(ما قبلاً در مورد مشکلاتی که در اینجا ممکن است رخ دهد صحبت کردیم). همانطور که قبلاً متوجه شدید، تغییرات ایجاد شده در برخی از رشته ها همیشه برای رشته های دیگر قابل مشاهده نیست. اما، البته، موقعیت های بسیار مکرری وجود دارد که چنین رفتاری برای ما مناسب نیست. فرض کنید به یک متغیر روی thread مقداری اختصاص می دهیم A:
int z;.

z = 555;
اگر Bموضوع ما باید مقدار zمتغیر را در کنسول نمایش دهد، به راحتی می تواند 0 را نمایش دهد، زیرا از مقدار اختصاص داده شده اطلاعی ندارد. zاما قانون 4 تضمین می کند که اگر متغیر را به عنوان اعلان کنیم volatile، تغییرات مقدار آن در یک رشته همیشه در رشته دیگر قابل مشاهده خواهد بود. volatileاگر به کلمه کد قبلی اضافه کنیم ...
volatile int z;.

z = 555;
...سپس از موقعیتی جلوگیری می کنیم که رشته Bممکن است 0 را نشان دهد. نوشتن روی volatileمتغیرها قبل از خواندن از آنها اتفاق می افتد.
نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION