menusearch
javapro.ir

کلید واژه ی volatile در زبان برنامه نویسی جاوا

جستجو
دوشنبه ۱۸ تیر ۱۴۰۳ | ۵:۵۴:۴۵
۱۴۰۲/۱۱/۳۰ دوشنبه
(10)
(0)
کلید واژه ی volatile در زبان برنامه نویسی جاوا
کلید واژه ی volatile در زبان برنامه نویسی جاوا

فرض کنید دو Thread دارید. یکی از این نخ ها مقداری را تغییر میدهد و دیگری بر مبنای تغییر داده شد دستوراتی اجرا میکند. کد زیر را در نظر بگیرید :

 

 

کد
public class NoVisibility {
  private static boolean ready=false;
  private static int number=0;
  private static class ReaderThread extends Thread {
      public void run() {
          while (!ready)
              Thread.yield();
          System.out.println(number);
      }
}
  public static void main(String[] args) throws InterruptedException {
      new ReaderThread().start();
      number = 42;
      ready = true;
  }
}

 

 

 

نکته : متد yield برای رها کردن پردازنده مورد استفاده قرار میگیرد. درواقع برای مدت کوتاهی پردازنده رها شده و در اولین فرصت مجددا به ترد اختصاص میابد.

 

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

 

میدانیم که برنامه های چند نخی درواقع به دو صورت اجرا خواهند شد : 1- به صورت همروند 2- به صورت موازی

در همروند توازی واقعی نداریم. تنها در صورتی توازی واقعی ممکن است داشته باشیم که تعداد هسته ها بیش از یک باشد. 

 

 

 

 

 

آموزش چند نخی(Multithreading) در زبان برنامه نویسی جاوا[کلیک کنید] : در دوره آموزش پیشرفته جاوا در فصل 12 به مباحث چندنخی (تردها) در زبان برنامه نویسی جاوا پرداخته ایم.

 

 

 

 

 

خطای دید (Visibility Problem):

 

Parallel

 

 

اگر نخست ترد Main بتواند مقدار ready را عوض کند و سپس ReaderThread این مقدار را بخواند. ترد دوم در حلقه ی بی نهایت گیر نمی‌افتد .در غیر اینصورت ممکن است ReaderThread همواره اجرا گردد. در صورتی که Main Thread نتواند در ابتدا مقدار ready را true کند, ترد دوم مقدار Ready که ابتدا false بوده را در رجیستر های هسته ذخیره میکند و تغییراتی که Main Thread بر روی ready اعمال میکند بر رجیستر های هسته ای که ReaderThread را اجرا میکند منعکس نمیشود. بنابراین دو متغیر Ready داریم یکی در رجیستر های پردازنده اول که مربوط به Main Thread است و دیگری در پردازنده ی دوم که مربوط به ReaderThread است.این اتفاق تحت عنوان Reordering شناخته میشود(یعنی دستورات به ترتیبی که نوشتیم و انتظار داریم اجرا نمیشوند). درواقع تضمینی وجود ندارد که چه زمانی کدام Thread مقدار Ready را میخواند. درواقع ترد دوم ممکن است مقداری که Stale (کهنه) است را ببیند.مشکلی که نخ ها نمیتوانند اخرین مقدار یک متغیر را ببینند Visibility Problem نامیده میشود.

 

وقتی یک نخ، متغیری را بدون همگام سازی میخواند، ممکن است مقداری کهنه (Stale) ببیند. اما حداقل مقداری را میبند که توسط یک نخ دیگر ذخیره شده و صرفا بروز نیست. درواقع مقداری تصادفی نمیبند. چنین ضمانتی، ضمانت out-of-thin-air نامیده میشود.

چنین ضمانتی یک استثنا دارد و آن هم متغیر های 64 بیت هستند(long, double). عملیات های خواندن و نوشتن در مقادیر 64 بیتی که volatile نیستند بصورت دو خواندن و نوشتن مجزای 32 بیتی انجام میشوند(در زمانی که جاوا نوشته شد بیشتر پردازنده ها بصورت غیر بهینه عملیات بر روی متغیر های 64 بیتی را ارائه میدادند). در این حین اگر دو ترد داشته باشیم که یکی میخواند و دیگری مینویسد، ممکن است عملیات نوشتن در 32 بیت اول انجام شود و خواندن در همین حین انجام شده و سپس 32 بیت بعدی نوشته شوند در اینصورت مقداری مشاهده خواهیم کرد که کاملا غیر منتظره و گویی تصادفی است.

 

اتمیک بودن (Atomicity)

عملیات اتمیک یعنی عملیاتی که بدون دستکاری نخ های دیگر انجام شود.

 

متغیر های Volatile

 

با اعلان یک متغیر بصورت volatile به کامپایلر میگوییم که از reordering جلوگیری کن. کامپایلر بدین صورت عمل میکند که اگر هر دو عملیات read و write برای یک متغیر ببیند ابتدا عملیات write و سپس read را انجام میدهد که منجر به جلوگیری از داده های Stale (کهنه ) میشود.

درواقع متغیر های volatile صرفا برای Visibility Problem استفاده میشوند.

 

محدودیت های متغیر های Volatile

 

متغیر های Volatile به اندازه ی کافی قوی نیستند که یک increment operation(count++ مثلا ) را بصورت Atomic پیاده سازی کنند مگر اینکه تضمین شود نوشتن متغیر صرفا توسط یک نخ انجام میپذیرد. راه حل : استفاده از Synchronized.

 

Synchronized میتواند هم visibility و هم atomicity را گارانتی کند درحالی که volatile صرفا visibility را گارانتی میکند.

 

چرا متغیر های Volatile در زمینه ی Increment operation ضعیف هستند؟

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

 

چه زمانی از متغیر های volatile استفاده کنیم ؟

  • زمانی که نوشتن و تغییر متغیر وابسته به مقدار خود متغیر نباشد (مثلا increment وابسته به مقدار خودش است) و یا اگر باشد تضمین شود که صرفا یک نخ آن را تغییر دهد چرا که ممکن است دو نخ همزمان اقدام به تغییر کنند و صرفا یکی از تغییرات اعمال شود.مثال :

فرض کنید a=1 و دو نخ میخواهند a++ را اجرا کنند .اگر دقیقا همزمان a افزایش دهند هر دو در ابتدا مقدار a=1 را مشاهده و سپس a++ میکنند یعنی در اخر a=2:

ترد A مقدار a را میبند(که یک است)

ترد B مقدار a را میبند(که یک است)

ترد A مقدار a را با یک جمع میکند و حاصل را در a قرار میدهد(که دو میشود)

ترد B مقدار a را با یک جمع میکنند و حاصل را در a قرار میدهد(که دو میشود)

 

در شرایط بالا میتوانیم از کلاس های اماده جاوا مثل AtomicInteger که عملیات خواندن نوشتن و تغییر دادن متغیر را بصورت اتمیک ارائه میدهند استفاده کنیم(در مقاله ای جدا به اینکه چطور همچین عملیاتی را بصورت اتمیک ارائه میدهند خواهیم پرداخت).

 

  • زمانی که استفاده از synchronized و دیگر روش های Lock نیازی نیست (درواقع volatile از synchronized با وجود ضعیف تر بودن سریعتر است )

 

برای اینکه بتوانیم شرایطی را فراهم اوریم که یک متغیر volatile بتواند increment شود(در حالت کلی بتواند به مقدار خودش وابسته باشد و بر مبنای ان تغییر یابد) بدون اینکه نخ ها را محدود کنیم، بایستی کاری کنیم که عملیات خواندن مقدار متغیر و افزایش ان بصورت Atomic صورت بگیرد. یعنی مثلا اگر نخ A مقدار a را یک دید، بصورت متوالی و Atomic افزایش هم انجام شود در این صورت ترد B مقدار 2 را میبیند.برای این منظور از الگوریتمی به نام Compare and Swap استفاده میکنیم که در کلاس های Atomic جاوا مثل AtomicInteger از همین الگوریتم استفاده میشود.

 

Compare and Swap

برای اینکه بتوانیم عملیات Read و Write را بصورت Atomic انجام دهیم از چنین الگوریتمی استفاده میکنیم. مثلا فرض کنید میخواهیم عملیات increment را بصورت Atomic پیاده سازی کنیم :

 

Compare and Swap

class EmulatedCAS {
    private int value;

    public synchronized int getValue() {
        return value;
    }

    public synchronized int compareAndSwap(int expectedValue, int newValue) {
        int readValue = value;
        if (readValue == expectedValue)
            value = newValue;
        return readValue;
    }
}

class Counter {
    private EmulatedCAS value = new EmulatedCAS();

    public int getValue() {
        return value.getValue();
    }

    public int increment() {
        int readValue = value.getValue();
        while (value.compareAndSwap(readValue, readValue + 1) != readValue)
            readValue = value.getValue();
        return readValue + 1;
    }
}

 

همانطور که مشاهده میکنید موقعی که میخواهیم مقدار متغیر را تغییر دهیم. عملیات خواندن و نوشتن درون بلاک synchronized انجام میپذیرد و این یعنی در زمان خواندن و قبل از تغییر دادن مقدار هیچ نخ دیگری نمیتواند بخواند پس مشکلات ذکر شده دیگر رخ نمیدهند.

 

منابع

  • Java cuncurrency in practice
  • Jenkov.com
  • https://github.com/mtumilowicz/java-concurrency-compare-and-swap

 

نظرات کاربران
*نام و نام خانوادگی
* پست الکترونیک
* متن پیام

بستن
*نام و نام خانوادگی
* پست الکترونیک
* متن پیام

1 نظر
ساناز اردشیری
دوشنبه سی ام بهمن ۰۲
پاسخ
()
()
ساناز اردشیری
بسيار جامع و مفيد بود خداقوت
پاسخ مدیر سایت
خوش حالم مفید بوده
پاسخ مدیر سایت
هدر سایت
مشاهده سرفصل ها و ثبت نام در دوره Spring Boot جاواپرو  [کلیک کنید]
آموزش پروژه محور اسپرینگ بوت(Spring Boot)-سیستم دانشگاه
دوره پرتاب | آموزش پیش نیازهای برنامه نویسی
دوره آموزش مبانی زبان برنامه نویسی جاوا
دوره آموزش مفاهیم پیشرفته زبان برنامه نویسی جاوا
مقدمه ای از زبان برنامه نویسی جاوا(java)
آموزش زبان برنامه نویسی جاوا
آموزش گرافیک در زبان برنامه نویسی جاوا
آموزش مدیریت چیدمان گرافیکی در زبان جاوا
آموزش ساخت بازی دوبعدی در زبان جاوا
Collection ها در زبان برنامه نویسی جاوا
آموزش پروژه محور ساخت برنامه مدیریت بانک با JavaFX
نمونه پروژه های رایگان زبان جاوا
آموزش دیتابیس در زبان برنامه نویسی جاوا
نمونه مثال پایه ای زبان برنامه نویسی جاوا
نمونه مثال String در زبان برنامه نویسی جاوا
آموزش جامع برنامه نویسی JavaFX
آموزش ساخت برنامه آزمون تستی در JavaFX
آموزش برنامه نویسی سوکت در جاوا
آموزش ساخت برنامه دفترچه تلفن با JavaFX
آموزش ساخت ربات ساده تلگرام با زبان جاوا
آموزش ساخت برنامه ماشین حساب با JavaFX
آموزش ساخت برنامه ساده مدیریت ایمیل ها با JavaFX
دوره آموزش Spring Boot
سفارش انجام پروژه زبان برنامه نویسی جاوا(JAVA)
سفارش انجام پروژه برنامه نویسی متلب(MATLAB) با قیمت منصفانه و تحویل به موقع
سفارش انجام پروژه زبان برنامه نویسی سی شارپ (#C)
سفارش انجام پروژه زبان برنامه نویسی سی(C)
سفارش انجام پروژه زبان برنامه نویسی پایتون(Python)
سفارش انجام پروژه زبان برنامه نویسی PHP (پی اچ پی)
سفارش انجام پروژه زبان برنامه نویسی اسمبلی(Assembly)
سفارش انجام پروژه زبان برنامه نویسی جاوا اسکریپت (Javascript)
سفارش انجام پروژه هوش مصنوعی
سفارش انجام پروژه طراحی الگوریتم
سفارش انجام پروژه ساختمان داده ها
سفارش انجام پروژه مهندسی نرم افزار
سفارش انجام پروژه شبکه های کامپیوتری
سفارش انجام پروژه پایگاه داده: دیتابیس (database)
 سفارش انجام پروژه سیستم عامل
سفارش انجام پروژه پاورپوینت(PowerPoint)
سفارش انجام پروژه اکسل (Excel)
سفارش انجام تحقیق و تهیه مقاله
سوالات متداول برنامه نویسی
جدیدترین مطالب
گفتگو را شروع کنید
مشاوره ،تدریس خصوصی و سفارش انجام انواع پروژه های برنامه نویسی