نحوه حل مشکل تولیدکننده-مصرف کننده در جاوا با استفاده از Multithreading
همزمانی بخش مهمی از برنامه های جاوا است. هر برنامه دارای چندین فرآیند در حال اجرا به طور همزمان است. این به استفاده موثر از منابع و بهبود عملکرد کمک می کند.
Multithreading روشی برای دستیابی به همزمانی است. از مفهوم thread ها - فرآیندهای سبک وزن - برای اجرای چندین کار به صورت موازی استفاده می کند. یکی از کاربردهای بسیار پرطرفدار Multithreading مسئله تولیدکننده-مصرف کننده است.
در این آموزش، میخواهیم بفهمیم که مشکل تولیدکننده-مصرفکننده چیست و به طور خلاصه به رشتهها و چند رشتهبندی اشاره میکنیم. سپس، میخواهیم بفهمیم که چگونه مشکل تولیدکننده-مصرفکننده را در جاوا با استفاده از نخها حل کنیم.
در حال حاضر، من فرض می کنم که شما دانش اولیه جاوا دارید. اگر نه، پس منابع زیر را تحلیل کنید.
دوره های رایگان جاوا برای مبتدیان
فهرست مطالب
مشکل تولید کننده و مصرف کننده چیست؟
راه حل با استفاده از موضوعات تولید کننده و مصرف کننده و مشکل با همگام سازی
معرفی همگام سازی در کلاس صف پیام
تولید کننده با مصرف کنندگان متعدد
راه حل با استفاده از کلاس BlockingQueue در Java Concurrency
مشکل تولید کننده و مصرف کننده چیست؟
مشکل تولیدکننده-مصرف کننده یک مشکل همگام سازی بین فرآیندهای مختلف است. سه موجودیت در این مشکل وجود دارد: یک تولید کننده، یک مصرف کننده و یک بافر حافظه. هم تولیدکننده و هم مصرف کننده حافظه بافر یکسانی دارند.
سازنده برخی از آیتم ها را تولید می کند و آنها را به بافر حافظه فشار می دهد. سپس یک مصرف کننده این اقلام را با بیرون ریختن آنها از بافر مصرف می کند. اگر بافر خالی باشد، مصرفکننده منتظر میماند تا تولیدکننده کالایی را فشار دهد که پس از فشار دادن تولیدکننده، آن را مصرف میکند.
بافر حافظه اندازه ثابتی دارد. اگر پر باشد، تولیدکننده منتظر می ماند تا مصرف کننده یک کالا را قبل از فشار دادن کالای جدید مصرف کند. تولید کننده و مصرف کننده نمی توانند به طور همزمان به بافر دسترسی داشته باشند - یعنی متقابلاً منحصر به فرد است. هر فرآیند باید منتظر بماند تا دیگری کار خود را روی بافر تمام کند تا بتواند به بافر دسترسی پیدا کند.
سیستم عامل ها اغلب با این مشکل مواجه می شوند که در آن چندین پردازش برای انجام وظایف خود به فضای حافظه یکسان دسترسی دارند.
ما این مشکل را با استفاده از multithreading حل خواهیم کرد، پس من فرض میکنم که شما یک ایده اولیه در مورد اینکه multithreading چیست و چگونه کار میکند دارید. اگر نه، پس می توانید این آموزش را بخوانید .
ما با تلاش برای حل آن به سادگی با استفاده از موضوعات و یک کلاس جداگانه برای صف پیام شروع می کنیم. سپس، مشکلات آن و چگونگی غلبه بر آنها را در رویکرد بعدی درک خواهیم کرد. همچنین رویکردهای دیگری برای حل این مشکل خواهیم دید. اطمینان حاصل کنید که تا انتها بچسبید.
راه حل با استفاده از موضوعات تولید کننده و مصرف کننده
بیایید ابتدا نیازهای خود را مرور کنیم.
وظایف تولید کننده و مصرف کننده در رشته های جداگانه اجرا می شود
گذرگاه داده رایج، معمولاً یک صف پیام است که هم توسط تولید کننده و هم مصرف کننده استفاده می شود.
اگر پر نباشد، تولیدکننده داده ها را در صف قرار می دهد یا منتظر می ماند تا مصرف شود
اگر خالی نباشد، مصرفکننده دادهها را از صف خارج میکند یا منتظر میماند تا تولیدکننده منتشر کند.
اینها مواردی است که برای حل این مشکل باید اجرا کنیم. بیایید ابتدا صف پیام را ایجاد کنیم.
صف پیام
برای تنظیم صف پیام، از کلاسی استفاده میکنیم که حاوی صف و متدهای انتشار و مصرف پیامها است.
class Data { Queue<String> q; int capacity; Data(int cap) { q = new LinkedList<>(); capacity=cap; } // other methods }
در اینجا، ما از کلاس Queue
جاوا برای ذخیره پیام های خود استفاده کرده ایم. هر پیام از نوع String
است. اما در برنامه های بزرگتر، پیام یا محموله می تواند از هر نوع شی باشد. ظرفیت صف پیام را هم تعریف می کنیم.
در مرحله بعد، متد publish()
را پیاده سازی می کنیم. این روش پیام جدیدی را برای انتشار می پذیرد.
public void publish(String msg) { // publish message to the queue }
ابتدا تحلیل می کنیم که آیا صف پر است یا خیر. اگر ظرفیت صف تکمیل شده باشد، نمیتوانیم پیام جدیدی منتشر کنیم.
if(q.size() == capacity){ return; }
اگر صف پر نیست، پیام را به صف اضافه کنید.
q.add(msg);
برای درک بهتر گردش کار، عبارات چاپی را اضافه می کنیم.
public void publish(String msg) { String name=Thread.currentThread().getName(); if(q.size() == capacity){ System.out.println("Queue Full!"+name+" waiting for message to be consumed..."); return; } q.add(msg); System.out.println("Message published:: "+msg); System.out.println("Queue: "+ q); System.out.println(); }
در اینجا، ما فقط موضوعی را که داخل متد است، پیام منتشر شده و صف حاصل را چاپ می کنیم.
بیایید اکنون متد consume()
را پیاده سازی کنیم. این متد هیچ آرگومان نمی گیرد و مشابه متد publish()
عمل می کند. ابتدا چک می کنیم که آیا صف خالی است، قبل از اینکه هر چیزی را از صف برداریم.
if(q.size()==0){ return; }
سپس، پیام را حذف می کنیم.
q.poll();
دوباره، برای درک بهتر گردش کار، دستورات چاپی را اضافه می کنیم.
public void consume() { String name=Thread.currentThread().getName(); if(q.size()==0){ System.out.println(name+" waiting for new message..."); return; } String msg = q.poll(); System.out.println(name+" has consumed msg:: "+msg); System.out.println("Queue: "+ q); System.out.println(); }
موضوع تولید کننده
حالا منطق تولیدکننده را بنویسیم. ما یک کلاس Producer
ایجاد می کنیم که در یک رشته اجرا می شود. راه های مختلفی برای ایجاد Thread در جاوا وجود دارد. ما از رابط Runnable
برای ایجاد رشته خود استفاده خواهیم کرد زیرا این روش ترجیح داده شده است.
class Producer implements Runnable{ Data data; public Producer(Data data) { this.data = data; } @Override public void run() { } }
تولید کننده به شی گذرگاه داده دسترسی دارد که از طریق سازنده به آن ارسال می شود. منطق تولیدکننده داخل متد run()
می رود. با نادیده گرفتن روش اجرا، عملکردی را می نویسید که در یک رشته اجرا می شود.
اکنون، روشهای تولید و انتشار دادههای تولیدکننده در هر برنامه متفاوت است. برای این پست، میخواهیم عملکردی را شبیهسازی کنیم که در آن سازنده هر چند ثانیه یک بار پیامها را منتشر میکند.
ما فهرستی از پیامهایی را که تولیدکننده میتواند استفاده کند، تعریف میکنیم.
final String[] messages={"Hi!!", "How are you!!", "I love you!", "What's going on?!!", "That's really funny!!"};
در اینجا منطق تولید کننده در متد run()
آمده است:
public void run() { int i=0; try { while(true){ Thread.sleep(1000); data.publish(messages[i]); i=(i+1)%messages.length; } } catch (InterruptedException e) {} }
در این کد، تولیدکننده هر 1000 میلیثانیه یک پیام از فهرست پیامها منتشر میکند. Thread.sleep( some_delay )
اجرای thread را برای مدت معینی متوقف می کند. از آنجایی که یک استثنا ایجاد می کند، ما کد را با یک بلوک try-catch احاطه می کنیم.
این فقط برای اهداف نمایشی است - نگران منطق نباشید. اجرای ما بدون توجه به منطق تولید کننده یا مصرف کننده کار می کند.
موضوع مصرف کننده
مشابه موضوع تولید کننده، بیایید منطق مصرف کننده را شبیه سازی کنیم.
class Consumer implements Runnable{ Data data; public Consumer(Data data) { this.data = data; } @Override public void run() { try { while(true){ Thread.sleep(2000); data.consume(); } } catch (InterruptedException e) {} } }
در اینجا، مصرف کننده سعی می کند هر 2000 میلی ثانیه یک پیام را مصرف کند.
همه اش را بگذار کنار هم
اکنون صف پیام خود را به همراه صنف تولید کننده و مصرف کننده داریم. بیایید یک رشته تولیدکننده و یک مصرف کننده ایجاد کنیم و آنها را راه اندازی کنیم.
ما یک آبجکت Data
با ظرفیت 5 پیام ایجاد می کنیم و رشته های تولید کننده و مصرف کننده خود را با اشیاء کلاس های Producer
و Consumer
ایجاد می کنیم.
public class Main { public static void main(String[] args) { Data data = new Data(5); Thread producer=new Thread(new Producer(data), "producer"); Thread consumer=new Thread(new Consumer(data), "consumer"); producer.start(); consumer.start(); } }
با فراخوانی start()
run()
() در یک رشته مجزا اجرا می شود.
خروجی:
در اینجا، خروجی با گردش کار مورد نظر ناسازگار است. حتی پس از انتشار اولین پیام، مصرف کننده همچنان منتظر است. سپس یک مصرف کننده پیام Hi!!
که وجود ندارد وضعیت صف نیز ناسازگار است.
احتمالاً خروجی متفاوتی دریافت خواهید کرد، زیرا اجرای thread به سیستم عامل اصلی بستگی دارد. اما موضوع همچنان پابرج است. چرا این اتفاق می افتد؟
مشکل همگام سازی با کلاس داده
هر دو رشته تولید کننده و مصرف کننده به طور همزمان اجرا می شوند و روی یک منبع کار می کنند. آنها دقیقاً در همان زمان به صف پیام دسترسی پیدا می کنند. هر دو رشته ممکن است با یک نسخه از منبع شروع شوند و زمانی که عملیاتی را انجام می دهند، روی نسخه دیگری کار می کنند.
این منجر به شرایط مسابقه می شود. هر دو رشته در نهایت برای یک منبع به رقابت می پردازند و نتایج متناقضی می دهند. موضوع تولید کننده در تلاش است تا یک پیام را به صف اضافه کند، در حالی که در همان زمان یک مصرف کننده در تلاش است تا یک پیام را مصرف کند. هیچ راهی برای کنترل پیامی که مصرف کننده می تواند دریافت کند وجود ندارد.
برای حل این مشکل، ما به نوعی مکانیسم نیاز داریم تا اطمینان حاصل کنیم که تنها یک رشته میتواند در یک منبع مشترک کار کند. در این مورد، از مفهوم همگام سازی استفاده خواهیم کرد.
در یک نگاه، یک تابع هماهنگ یا یک بلوک تنها می تواند توسط یک رشته در یک زمان اجرا شود. نخی که وارد چنین بلوکی می شود یک "قفل" روی شی پیدا می کند و هر رشته دیگری باید منتظر بماند تا نخ این قفل را آزاد کند - یعنی تا زمانی که کار روی منبع مشترک تمام شود.
ما از یک روش مشابه برای حل مشکل خود استفاده خواهیم کرد.
معرفی همگام سازی در صف پیام
برای اطمینان از دسترسی به صف پیام فقط توسط یک رشته (تولید کننده یا مصرف کننده) در یک زمان، یک رشته باید قبل از انجام هر عملیاتی، قفلی را روی شی Data
ایمن کند.
نحوه استفاده از کلمه کلیدی synchronized
یک شی را می توان با استفاده از کلمه کلیدی همگام سازی شده متقابلاً منحصر به فرد کرد. ما از کلمه کلیدی synchronized
با متدهای publish()
و consume()
استفاده می کنیم.
public synchronized void publish(String msg)
public synchronized void consume()
اکنون، یک نخ باید قبل از وارد شدن به این روشها، یک قفل روی شی را به دست آورد.
متدهای wait()
و notify()
چیست؟
ما به همگام سازی دست یافته ایم - فقط یک رشته می تواند در هر زمان به یک منبع مشترک دسترسی داشته باشد تا از ثبات اطمینان حاصل شود. در حال حاضر، ما باید بین رشته های تولید کننده و مصرف کننده ارتباط برقرار کنیم.
بیایید ابتدا بفهمیم که به چه چیزی نیاز داریم. اگر صف پر باشد، تولیدکننده باید منتظر مصرف کننده باشد تا یک کالا را مصرف کند. به طور مشابه، اگر صف خالی باشد، مصرف کننده باید منتظر بماند تا تولید کننده یک کالا را فشار دهد.
همچنین، هنگامی که یک تولیدکننده کالایی را هل میدهد، باید به تمام موضوعات مصرفکننده منتظر درباره این اقدام اطلاع دهد. همچنین زمانی که مصرف کننده یک کالا را مصرف می کند، صادق است. پس ، چگونه این ارتباط را برقرار کنیم؟
ما میتوانیم این کار را با استفاده از متدهای wait()
و notify()
انجام دهیم. هنگامی که متد wait()
فراخوانی می شود، رشته قفل روی شی را آزاد می کند و تا زمانی که رشته دیگری متد notify()
یا notifyAll()
را فراخوانی کند وارد حالت انتظار می شود.
notify()
یک رشته را که در حالت انتظار است بیدار می کند، در حالی که notifyAll()
همه رشته های منتظر را بیدار می کند. هنگامی که یک نخ دوباره بیدار می شود، باید دوباره قفل روی جسم را به دست آورد. اگر نخ دیگری قفل را داشته باشد، این موضوع باید منتظر بماند تا نخ دیگر قفل را آزاد کند.
در اینجا میتوانید درباره روشهای wait()
و notify()
اطلاعات بیشتری کسب کنید.
نحوه برقراری ارتباط بین موضوعات با استفاده از wait() و notify()
بیایید از روش های بالا استفاده کنیم. اگر صف پر باشد، یک تولیدکننده باید قبل از فشار دادن یک آیتم صبر کند. پس ، اگر صف در ظرفیت است، متد wait()
را فراخوانی کنید. به طور مشابه، مصرف کننده باید منتظر بماند اگر صف خالی باشد.
برای بیدار کردن رشتهها از حالت انتظار، پس از انتشار پیام توسط سازنده و مصرفکننده پیام، با notifyAll()
تماس بگیرید. این به تمام موضوعات منتظر اطلاع می دهد.
در اینجا روش publish()
به روز شده است:
public synchronized void publish(String msg) throws InterruptedException { String name=Thread.currentThread().getName(); if(q.size() == capacity){ System.out.println("Queue Full!"+name+" waiting for message to be consumed..."); wait(); } q.add(msg); System.out.println("Message published:: "+msg); System.out.println("Queue: "+ q); System.out.println(); notifyAll(); }
و در اینجا روش consume()
به روز شده است:
public synchronized void consume() throws InterruptedException { String name=Thread.currentThread().getName(); if(q.size()==0){ System.out.println(name+" waiting for new message..."); wait(); } String msg = q.poll(); System.out.println(name+" has consumed msg:: "+msg); System.out.println("Queue: "+ q); System.out.println(); notifyAll(); }
wait()
و notify()
می توانند InterruptedException
پرتاب کنند، پس ما یک اعلان به متدها throws
می کنیم.
خروجی:
این بار، خروجی سازگارتر است و ما رفتار مورد انتظار را دریافت می کنیم. تولیدکننده به انتشار پیامها ادامه میدهد و مصرفکننده آن پیامها را مصرف میکند.
در اینجا صف پر است و تولیدکننده منتظر می ماند تا مصرف کننده پیامی را مصرف کند. تنها پس از آن تهیه کننده پیام جدیدی منتشر می کند.
تولید کننده با مصرف کنندگان متعدد
تاکنون با یک تولید کننده و یک مصرف کننده به مشکل رسیدگی کرده ایم. در یک برنامه دنیای واقعی، ممکن است چندین تولید کننده و مصرف کننده وجود داشته باشد که همه آنها در رشته های جداگانه اجرا می شوند.
بیایید موضوعات مصرف کننده بیشتری اضافه کنیم تا ببینیم چگونه می توانیم این سناریو را مدیریت کنیم:
for(int i=1;i<=5;i++){ new Thread(new Consumer(data), "Consumer "+i).start(); }
در اینجا، ما 5 موضوع مصرف کننده ایجاد کرده ایم، آنها را برچسب گذاری کرده ایم و آنها را یکی یکی شروع کرده ایم. اما این کافی نیست. در رویکرد موجود مشکلی وجود دارد.
بیایید زمان خواب مصرف کننده را کاهش دهیم و کد را اجرا کنیم:
Thread.sleep(500);
در اینجا، پس از اینکه مصرف کننده 5 پیامی را مصرف کرد، سایر مصرف کنندگان می توانند حتی اگر صف خالی باشد، مصرف کنند.
موضوع در شرایط زیر نهفته است:
if(q.size()==0){ wait(); }
بیایید ابتدا روند کار را درک کنیم. مصرف کننده 5 (C5) و مصرف کننده 1 (C1) را در نظر بگیرید. C5 قفل روی روش را محکم می کند و وارد آن می شود. صف ابتدا خالی است، پس قفل را آزاد می کند و منتظر تولید کننده می ماند. همزمان C1 قفل را محکم می کند و وارد روش می شود. همچنین منتظر تولید کننده است.
پس ، C5 و C1 منتظر هستند. تهیه کننده پیامی را منتشر می کند. C5 و C1 مطلع می شوند و بیدار می شوند. C5 دوباره قفل را دریافت می کند و پیام را مصرف می کند، در حالی که C1 منتظر می ماند تا C5 قفل را آزاد کند. در اینجا، C1 به دلیل wait()
منتظر نیست – بیدار شده و اکنون در خط بعدی منتظر است.
بعد از اینکه C5 پیام را مصرف کرد و قفل را آزاد کرد، C1 ادامه می دهد و سعی می کند پیام را مصرف کند. اما صف در حال حاضر خالی است، پس null دریافت می کند یا یک استثنا ایجاد می کند. این موضوع در مورد رشته های دیگر نیز اتفاق می افتد.
برای جلوگیری از این امر، باید یک بار دیگر تحلیل کنیم که آیا صف خالی است یا خیر. پس ، به جای استفاده از شرط if
، از حلقه while
مانند زیر استفاده می کنیم:
while(q.size()==0){ wait(); }
با این کار هر بار که یک رشته از خواب بیدار می شود، صف خالی است یا خیر. پس ، اگر چندین رشته به طور همزمان بیدار شوند، باید تحلیل کند که آیا رشته دیگری صف را خالی کرده است یا خیر.
همین کار را برای تحلیل پر بودن صف انجام می دهیم.
while(q.size() == capacity){ wait(); }
این بار کد بدون هیچ مشکلی اجرا می شود.
این کد کامل برای کلاس Data
است:
بیشتر بخوانید
کمپانی Space Perspective ظاهراً اولین کاوشگران خود را اواخر 2024 با بالنهای مجلل به لبه فضا میبرد
class Data { Queue<String> q; int capacity; Data(int cap) { q = new LinkedList<>(); capacity=cap; } public synchronized void publish(String msg) throws InterruptedException { String name=Thread.currentThread().getName(); while(q.size() == capacity){ System.out.println("Queue Full!"+name+" waiting for message to be consumed..."); wait(); } q.add(msg); System.out.println("Message published:: "+msg); System.out.println("Queue: "+ q); System.out.println(); notifyAll(); } public synchronized void consume() throws InterruptedException { String name=Thread.currentThread().getName(); while(q.size()==0){ System.out.println(name+" waiting for new message..."); wait(); } String msg = q.poll(); System.out.println(name+" has consumed msg:: "+msg); System.out.println("Queue: "+ q); System.out.println(); notifyAll(); } }
شما می توانید هر تعداد تولید کننده و مصرف کننده ایجاد کنید و این کد را در چندین سناریو تست کنید.
راه حل با استفاده از کلاس BlockingQueue در Java Concurrency
تا اینجا متوجه شدید که مشکل تولیدکننده-مصرف کننده چیست و چگونه می توان آن را حل کرد. اما، در حالی که روی برنامههای بلادرنگ کار میکنیم، احتمالاً همگامسازی را به صورت دستی پیادهسازی نخواهیم کرد.
در عوض، میتوانیم از کلاس BlockingQueue
از بسته java.util.concurrent
استفاده کنیم. تفاوت بین Queue
و BlockingQueue
در این است که منتظر می ماند تا صف قبل از مصرف شدن یک پیام خالی نشود. به همین ترتیب، قبل از انتشار یک پیام جدید، منتظر می ماند تا صف خالی شود.
ما می توانیم صف مسدود کردن را به روش زیر مقداردهی کنیم:
BlockingQueue<String> q = new ArrayBlockingQueue<>(10);
این یک صف مسدود کننده با ظرفیت 10 ایجاد می کند. برای انتشار یک آیتم از متد put()
و برای حذف یک آیتم از متد take()
استفاده می کنیم.
بیایید ابتدا از این در کلاس Producer خود استفاده کنیم:
class Producer implements Runnable{ BlockingQueue<String> q; final String[] messages={"Hi!!", "How are you!!", "I love you!", "What's going on?!!", "That's really funny!!"}; public Producer(BlockingQueue<String> q) { this.q = q; } @Override public void run() { int i=0; try { while(true){ Thread.sleep(500); q.put(messages[i]); System.out.println("Message published:: "+messages[i]); i=(i+1)%messages.length; } } catch (InterruptedException e) {} } }
در اینجا، ما از یک کلاس Data
جداگانه با متدهای همگام سازی شده استفاده نمی کنیم، زیرا متدهای put()
و take()
BlockingQueue
همگام هستند. در اینجا، اگر صف پر باشد، متد put()
منتظر می ماند تا مصرف کننده پیامی را مصرف کند.
به طور مشابه، بیایید کلاس Consumer خود را به روز کنیم:
class Consumer implements Runnable{ BlockingQueue<String> q; public Consumer(BlockingQueue<String> q) { this.q = q; } @Override public void run() { try { while(true){ Thread.sleep(1500); String msg=q.take(); String name=Thread.currentThread().getName(); System.out.println(name+" has consumed msg:: "+msg); } } catch (InterruptedException e) {} } }
در اینجا، اگر صف خالی باشد، متد take()
منتظر می ماند تا سازنده پیامی را منتشر کند.
بیایید شی BlockingQueue
خود را ایجاد کنیم و این موضوعات را شروع کنیم:
public class Main { public static void main(String[] args) { BlockingQueue<String> q = new ArrayBlockingQueue<>(10); Thread producer = new Thread(new Producer(q)); producer.start(); for(int i=1;i<=5;i++){ new Thread(new Consumer(q), "Consumer "+i).start(); } } }
در اینجا، ما یک نخ تولید کننده و 5 رشته مصرف کننده داریم. خروجی را ببینیم:
در خروجی می بینید که پس از مصرف کنندگان 1، 3 و 2 یک پیام، سایر مصرف کنندگان قبل از مصرف منتظر انتشار پیامی هستند.
ممکن است در هنگام چاپ این پیامها چند تناقض وجود داشته باشد، زیرا موضوع فقط در متدهای put()
یا take()
متوقف میشود و نه در دستورات println()
. اما کد به درستی اجرا می شود. باز هم، خروجی هر بار که کد را اجرا می کنید متفاوت خواهد بود.
پس ، هنگام کار بر روی پروژه های بزرگ، می توانید از کلاس BlockingQueue
استفاده کنید. اما مهم این است که بدانیم چگونه می توان با کل مشکل تولیدکننده-مصرف کننده از ابتدا مقابله کرد. این واقعاً مفید است، به خصوص در طول مصاحبه، زیرا معمولاً اجازه استفاده از کلاس BlockingQueue
را ندارید.
نتیجه
در این آموزش با یک مشکل مهم در همزمانی، مشکل محصول – مصرف کننده آشنا شدید. و یاد گرفتید که چگونه می توانید آن را با استفاده از multithreading در جاوا حل کنید.
در مجموع، ما چهار رویکرد مختلف را اجرا کردیم:
اول، من با اجرای بسیار ابتدایی و ساده شروع کردم. اجرای تولید کننده و مصرف کننده در رشته های جداگانه به دستیابی به همزمانی کمک کرد. اما از آنجایی که آنها از همان صف پیام استفاده می کردند، مشکل همگام سازی وجود داشت.
پس ، در رویکرد بعدی، همگام سازی را برای رفع مشکل اضافه کردیم. سپس، مصرف کنندگان بیشتری را اضافه کردیم که همه منتظر تولیدکننده بودند. در آنجا، ما یاد گرفتیم که چرا مهم است که هر بار که یک موضوع بیدار می شود، شرایط پر و خالی را تحلیل کنیم.
پس از گذراندن کل پیاده سازی از ابتدا، یک راه ساده برای حل مشکل تولید کننده-مصرف کننده در جاوا با استفاده از BlockingQueue دیدیم. با درک رویکردهای مختلف و مسائل آنها، می توانید ایده بهتری در مورد چگونگی مقابله با یک مشکل به دست آورید. امیدوارم این راهنما به تلاش های آینده شما کمک کند.
اگر نمی توانید مطالب را درک کنید یا توضیح را رضایت بخش نمی دانید، به من اطلاع دهید. ایده های جدید همیشه قدردانی می شوند! با خیال راحت با من در توییتر ارتباط برقرار کنید. تا آن زمان، خداحافظ!
ارسال نظر