رایجترین مشکلات همروندی در جاوا

همروندی این امکان را میدهد که سیستم عامل به طور موازی چند کار را انجام دهد. با توجه به اینکه منابع سیستم بین ریسهها به اشتراک گذاشته میشود، ناسازگاری و مشکلاتی ممکن است رخ دهد که اغلب کشف آنها میتواند دشوار باشد. در این مطلب مشکلات رایج همروندی در جاوا را مطرح خواهیم کرد.
۱- یکی از رایجترین مشکلات برنامههای موازی فهمیدن این مساله است که هیچ تضمینی وجود ندارد که فیلدی که توسط یک ریسه نوشته شده، توسط ریسه دیگر دیده نشود. یک مثال آن به شکل زیر است:
class MyThread extends Thread { private boolean stop = false; public void run() { while(!stop) { doSomeWork(); } } public void setStop() { this.stop = true; } }
از آنجایی که stop به صورت volatile۱ تعریف نشده است و run و setStop نیز synchronized نیستند تضمینی وجود ندارد که این کد درست کار کند. این مشکل خیلی بدقلق است چرا که در ۹۹.۹۹۹ درصد مواقع در عمل دیده نمیشود چون که ریسه خواننده بالاخره تغییرات را خواهد دید، اما نمیدانیم چقدر زود آن را دیده است.
به علاوه ممکن است هیچ وقت دیده نشود. ممکن است JVM حلقه را به شکل while(true) بهینه کرده و مساله را پیچیده تر سازد.این اتفاق در VMهای سمت سرور فقط رخ میدهد، فقط در زمانی که JVM بعد از x تکرار حلقه، مجددا کامپایل میکند.
۲- از دیگر خطاهای رایج و دردناک در این زمینه وقتی رخ میدهد که دو کتابخانه متنباز مختلف کاری شبیه زیر را انجام میدهند.
private static final String LOCK = "LOCK"; // use matching strings // in two different libraries public doSomestuff() { synchronized(LOCK) { this.work(); } }
در نگاه اول یک مثال همگام سازی بدیهی به نظر میرسد. اما به این دلیل که رشتهها در جاوا intern میشوند و به عبارتی تمام رشتههایی که محتوای یکسانی داشته باشند در حافظهی یکسانی ذخیره میشوند، رشته “LOCK” به یک نمونه از java.lang.String تبدیل میشود حتی اگر کاملا جدا از هم تعریف شده باشند. واضح است که این مساله چه نتایج بدی میتواند داشته باشد و این مساله یکی از دلایلی است که اغلب ترجیح میدهند Lock را به شکل زیر تعریف کنند:
private static final Object LOCK = new Object();
۳- یکی از مشکلات قدیمی هم تغییر شئی که همگام سازی روی آن رخ میدهد، در بدنه حلقه همگامسازی روی آن است:
synchronized(foo) { foo = ... }
سایر ریسههای موازی بعد از آن روی شئی دیگر همگام میشود و این بلوک نمیتواند انحصار متقابل مورد انتظار شما را فراهم کند. در این زمینه گفته میشود که همگام سازی روی فیلدهای غیرنهایی (non- final) بعید است که بتواند معانی مفیدی داشته باشد.
۴- یکی از اشکالات رایج استفاده از کلاسهایی مثل Calendar یا SimpleDateFormat از چندین ریسه مختلف بدون همگامسازی است. این کلاسها thread-safe نیستند و دسترسی چند ریسه به آنها میتواند مشکلات عجیبی ایجاد کند.
۵- اغلب مردم به شکل زیر singleton را چک میکنند.
public Class MySingleton { private static MySingleton s_instance; public static MySingleton getInstance() { if(s_instance == null) { synchronized(MySingleton.class) { s_instance = new MySingleton(); } } return s_instance; } }
این هیچوقت کار نمیکند چرا که ممکن است ریسه دیگری درون بلوک همگام سازی رفته باشد و s_instance دیگر null نخواهد بود. پس تغییر زیر را اعمال میکنند تا آن را اصلاح کنند.
public static MySingleton getInstance() { if(s_instance == null) { synchronized(MySingleton.class) { if(s_instance == null) s_instance = new MySingleton(); } } return s_instance; }
اما این هم کار نمیکند. چون که مدل حافظهای جاوا از آن پشتیبانی نمیکند و شما لازم دارید s_instance را volatile کنید تا کار کند هرچند این هم روی جاوا ۵ به بعد کار خواهد کرد. افرادی که با مدل حافظهای جاوا آشنایی ندارند همیشه با چنین مشکلاتی روبرو میشوند.
شما با چه مشکلاتی در همروندی روبرو شدهاید؟ چه مسائل دیگری را در زمینه همروندی میشناسید؟
منابع:
۱متغیر volatile متغیری است که مقدار آن در حافظه اصلی ذخیره میشود و هر خواندن و نوشتنی از آن از حافظه اصلی انجام میشود.