JavaRush /وبلاگ جاوا /Random-FA /داستان دو تکرار کننده: استراتژی های اصلاح رقابتی در جاوا

داستان دو تکرار کننده: استراتژی های اصلاح رقابتی در جاوا

در گروه منتشر شد
نویسنده یادداشت Grzegorz Mirek، یک توسعه دهنده نرم افزار از کراکوف (لهستان) است. او حدود 6 سال پیش در حالی که هنوز در دانشگاه بود شروع به توسعه در جاوا کرد و از آن زمان تاکنون به طور خستگی ناپذیری مهارت های خود را در این زمینه تقویت کرده است. او به طور خاص به عملکرد و بهینه سازی JVM علاقه مند است، چیزی که عمدتاً در مورد آن در وبلاگ خود می نویسد .
داستان دو تکرار کننده: استراتژی های اصلاح رقابتی در جاوا - 1
برخی از محبوب ترین سوالات مصاحبه جاوا عبارتند از: تفاوت بین تکرارکننده های سریع و ایمن شکست در چیست؟ ساده‌ترین پاسخ به این سؤال این است: اگر مجموعه در طول تکرار تغییر کند، یک تکرارکننده سریع یک خطای ConcurrentModificationException را می‌اندازد، اما تکرارکننده ایمن اینطور نیست. اگرچه این کاملاً معنی‌دار به نظر می‌رسد، اما مشخص نیست که منظور مصاحبه‌کننده از عدم موفقیت چیست؟ مشخصات زبان جاوا این اصطلاح را در رابطه با تکرارکننده ها تعریف نمی کند. با این حال، چهار استراتژی اصلاح رقابتی وجود دارد.

اصلاح رقابتی

ابتدا اجازه دهید تعریف کنیم که اصلاح رقابتی (یا موازی) چیست. فرض کنید یک مجموعه داریم و وقتی تکرار کننده فعال است، تغییراتی رخ می دهد که از این تکرار کننده نمی آید. در این مورد، ما یک اصلاح رقابتی دریافت می کنیم. بگذارید یک مثال ساده برای شما بزنم: فرض کنید چندین رشته داریم. نخ اول تکرار می شود و نخ دوم عناصری را از همان مجموعه وارد یا حذف می کند. با این حال، هنگام اجرا در یک محیط تک رشته ای، می توانیم یک ConcurrentModificationException دریافت کنیم:
List<String> cities = new ArrayList<>();
cities.add(Warsaw);
cities.add(Prague);
cities.add(Budapest);

Iterator<String> cityIterator = cities.iterator();
cityIterator.next();
cities.remove(1);
cityIterator.next(); // генерирует ConcurrentModificationException

شکست سریع

قطعه کد بالا نمونه ای از یک تکرار کننده سریع شکست است . همانطور که می بینید، هنگام تلاش برای بازیابی عنصر دوم از تکرار کننده، یک ConcurrentModificationException پرتاب شد . چگونه یک تکرار کننده می داند که مجموعه از زمان ایجاد آن تغییر کرده است؟ برای مثال، مجموعه ممکن است دارای مهر تاریخ/زمان باشد، مثلا lastModified . هنگام ایجاد یک تکرار کننده، باید این فیلد را کپی کرده و در یک شی تکرار کننده ذخیره کنید. سپس، هر بار که متد next() فراخوانی می‌شود، به سادگی مقدار lastModified از مجموعه را با کپی از تکرارکننده مقایسه می‌کنید . یک رویکرد بسیار مشابه برای مثال در پیاده سازی کلاس ArrayList استفاده می شود . دارای یک متغیر نمونه modCount است که تعداد دفعاتی که لیست اصلاح شده را ذخیره می کند:
final void checkForComodification() {
   if (modCount != expectedModCount)
       throw new ConcurrentModificationException();
}
توجه به این نکته مهم است که تکرارکننده‌های سریع شکست بر اساس بهترین حالت کار می‌کنند، به این معنی که هیچ تضمینی وجود ندارد که یک ConcurrentModificationException در صورت تغییر همزمان ایجاد شود. بنابراین نباید به آنها تکیه کنید - بلکه باید از آنها برای شناسایی خطاها استفاده کنید. اکثر مجموعه های غیرهمزمان تکرار کننده های سریع شکست را ارائه می دهند .

سازگاری ضعیف

اکثر مجموعه‌های همزمان در بسته java.util.concurrent (مانند ConcurrentHashMap و اکثر Queue ) تکرارکننده‌های ضعیفی را ارائه می‌کنند. معنای این اصطلاح به خوبی در مستندات توضیح داده شده است :
  • آنها را می توان همزمان با سایر عملیات پردازش کرد
  • آنها هرگز یک ConcurrentModificationException پرتاب نمی کنند
  • آنها تضمین می‌شوند که عناصر موجود را در زمانی که تکرارکننده دقیقاً یک بار ایجاد شده است، طی می‌کنند و می‌توانند (اما لازم نیست) تغییرات بعدی را منعکس کنند.

عکس فوری

با این استراتژی، تکرار کننده با وضعیت مجموعه در زمان ایجاد آن مرتبط است - این یک عکس فوری از مجموعه است. هر گونه تغییر در مجموعه اصلی منجر به ایجاد نسخه جدیدی از ساختار داده های اساسی می شود. این عکس فوری ما را بدون تغییر می‌گذارد، بنابراین تغییرات مجموعه را که پس از ایجاد تکرارکننده رخ داده است، نشان نمی‌دهد. این تکنیک قدیمی خوب کپی روی نوشتن (COW) است . این به طور کامل مشکل تغییرات همزمان را حل می کند، بنابراین یک ConcurrentModificationException با این رویکرد ایجاد نمی شود. علاوه بر این، تکرارکننده‌ها از عملیاتی که عناصر را تغییر می‌دهند پشتیبانی نمی‌کنند. استفاده از مجموعه‌های کپی روی نوشتن بسیار گران است، اما اگر تغییرات کمتر از پیمایش‌های تکرار شونده اتفاق می‌افتد، استفاده از آنها منطقی است. به عنوان مثال می توان به کلاس های CopyOnWriteArrayList و CopyOnWriteArraySet اشاره کرد .

رفتار تعریف نشده

ممکن است در انواع مجموعه های قدیمی مانند Vector و Hashtable با رفتار تعریف نشده مواجه شوید . هر دو دارای تکرارگرهای سریع شکست استاندارد هستند ، اما علاوه بر این، امکان استفاده از پیاده‌سازی رابط Enumeration را می‌دهند و نمی‌دانند در صورت تغییر همزمان چگونه رفتار کنند. ممکن است با برخی از عناصر تکراری یا از دست رفته یا حتی استثناهای عجیب روبرو شوید. بهتره باهاشون بازی نکنی!
نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION