معرفی
بنابراین، ما می دانیم که جاوا دارای موضوعات است. شما می توانید در مورد آن در بررسی با عنوان Better together: Java and the Thread کلاس بخوانید. بخش اول - موضوعات اجرا . نخ ها برای انجام کار به صورت موازی ضروری هستند. این امر باعث میشود که این احتمال وجود داشته باشد که رشتهها به نحوی با یکدیگر تعامل داشته باشند. بیایید ببینیم چگونه این اتفاق می افتد و چه ابزارهای اساسی داریم.
بازده
() Thread.yield مبهم است و به ندرت استفاده می شود. به طرق مختلف در اینترنت توضیح داده شده است. از جمله برخی از افرادی که می نویسند صفی از رشته ها وجود دارد که در آن یک موضوع بر اساس اولویت های رشته پایین می آید. افراد دیگر می نویسند که یک موضوع وضعیت خود را از "در حال اجرا" به "قابل اجرا" تغییر می دهد (حتی اگر هیچ تمایزی بین این وضعیت ها وجود ندارد، یعنی جاوا بین آنها تمایز قائل نمی شود). واقعیت این است که همه چیز بسیار کمتر شناخته شده و در عین حال به یک معنا ساده تر است.
yield()
مستندات روش ثبت شده است. اگر آن را بخوانید، واضح است که این yield()
روش در واقع فقط توصیههایی را به زمانبندی رشته جاوا ارائه میکند که میتوان زمان اجرای کمتری به این رشته داد. اما اینکه واقعاً چه اتفاقی میافتد، یعنی اینکه آیا زمانبند به توصیهها عمل میکند و به طور کلی چه کاری انجام میدهد، به پیادهسازی JVM و سیستم عامل بستگی دارد. و ممکن است به عوامل دیگری نیز بستگی داشته باشد. همه سردرگمی ها به احتمال زیاد به این دلیل است که همزمان با توسعه زبان جاوا، multithreading تجدید نظر شده است. اطلاعات بیشتر را در نمای کلی اینجا بخوانید: معرفی مختصر جاوا () Thread.yield
.
خواب
یک نخ می تواند در حین اجرای خود به خواب برود. این ساده ترین نوع تعامل با موضوعات دیگر است. سیستم عاملی که ماشین مجازی جاوا را اجرا می کند که کد جاوا ما را اجرا می کند، زمانبندی رشته خود را دارد . این تصمیم می گیرد که کدام رشته و چه زمانی شروع شود. یک برنامه نویس نمی تواند مستقیماً از طریق کد جاوا و فقط از طریق JVM با این زمانبند تعامل داشته باشد. او میتواند از برنامهریز بخواهد که موضوع را برای مدتی متوقف کند، یعنی آن را به خواب ببرد. میتوانید در این مقالات بیشتر بخوانید: Thread.sleep() و Multithreading چگونه کار میکند . همچنین میتوانید نحوه عملکرد رشتهها در سیستمعاملهای ویندوز را بررسی کنید: Internals of Windows Thread . و حالا بیایید آن را با چشمان خود ببینیم. کد زیر را در فایلی به نام ذخیره کنیدHelloWorldApp.java
:
class HelloWorldApp {
public static void main(String []args) {
Runnable task = () -> {
try {
int secToWait = 1000 * 60;
Thread.currentThread().sleep(secToWait);
System.out.println("Woke up");
} catch (InterruptedException e) {
e.printStackTrace();
}
};
Thread thread = new Thread(task);
thread.start();
}
}
همانطور که می بینید، ما یک کار داریم که 60 ثانیه منتظر می ماند و پس از آن برنامه به پایان می رسد. با استفاده از دستور " javac HelloWorldApp.java
" کامپایل می کنیم و سپس با استفاده از " " برنامه را اجرا می کنیم java HelloWorldApp
. بهتر است برنامه را در یک پنجره جداگانه شروع کنید. برای مثال در ویندوز به این صورت است: start java HelloWorldApp
. ما از دستور jps برای دریافت PID (شناسه فرآیند) استفاده می کنیم و لیست رشته ها را با " باز می کنیم jvisualvm --openpid pid
: 
try {
TimeUnit.SECONDS.sleep(60);
System.out.println("Woke up");
} catch (InterruptedException e) {
e.printStackTrace();
}
آیا متوجه شده اید که ما در حال رسیدگی InterruptedException
به همه جا هستیم؟ بیایید بفهمیم چرا.
Thread.interrupt()
مسئله این است که در حالی که یک موضوع در انتظار/خواب است، ممکن است کسی بخواهد حرفش را قطع کند. در این مورد، ما یکInterruptedException
. Thread.stop()
این مکانیسم پس از اعلام منسوخ شدن روش، یعنی منسوخ و نامطلوب ایجاد شد . دلیل آن این بود که وقتی stop()
روش فراخوانی شد، موضوع به سادگی "کشته شد" که بسیار غیرقابل پیش بینی بود. ما نمیتوانستیم بدانیم چه زمانی موضوع متوقف میشود، و نمیتوانیم ثبات دادهها را تضمین کنیم. تصور کنید که در حال نوشتن داده ها بر روی یک فایل هستید در حالی که موضوع کشته شده است. سازندگان جاوا به جای از بین بردن موضوع، تصمیم گرفتند که منطقی تر است که به آن بگویند که باید قطع شود. نحوه پاسخگویی به این اطلاعات موضوعی است که خود تاپیک تصمیم می گیرد. برای جزئیات بیشتر، بخوانید چرا Thread.stop منسوخ شده است؟
در وب سایت اوراکل بیایید به یک مثال نگاه کنیم:
public static void main(String []args) {
Runnable task = () -> {
try {
TimeUnit.SECONDS.sleep(60);
} catch (InterruptedException e) {
System.out.println("Interrupted");
}
};
Thread thread = new Thread(task);
thread.start();
thread.interrupt();
}
در این مثال، ما 60 ثانیه منتظر نخواهیم بود. در عوض، ما بلافاصله "وقفه" را نمایش خواهیم داد. این به این دلیل است که ما interrupt()
متد را در thread فراخوانی کردیم. این روش یک پرچم داخلی به نام "وضعیت وقفه" تنظیم می کند. یعنی هر رشته دارای یک پرچم داخلی است که مستقیماً قابل دسترسی نیست. اما ما روش های بومی برای تعامل با این پرچم داریم. اما این تنها راه نیست. یک رشته ممکن است در حال اجرا باشد، منتظر چیزی نباشد، به سادگی اقداماتی را انجام دهد. اما ممکن است پیش بینی کند که دیگران بخواهند کار خود را در یک زمان خاص به پایان برسانند. مثلا:
public static void main(String []args) {
Runnable task = () -> {
while(!Thread.currentThread().isInterrupted()) {
// Do some work
}
System.out.println("Finished");
};
Thread thread = new Thread(task);
thread.start();
thread.interrupt();
}
در مثال بالا، while
حلقه تا زمانی که thread به صورت خارجی قطع شود اجرا می شود. در مورد isInterrupted
پرچم، مهم است که بدانیم اگر یک را بگیریم InterruptedException
، پرچم isInterrupted دوباره تنظیم می شود و سپس isInterrupted()
false برمی گردد. کلاس Thread همچنین دارای یک متد ثابت () Thread.interrupted
است که فقط برای رشته فعلی اعمال می شود، اما این متد پرچم را به false بازنشانی می کند! در این فصل با عنوان قطع موضوع
بیشتر بخوانید .
بپیوندید (منتظر بمانید تا موضوع دیگری تمام شود)
ساده ترین نوع انتظار، انتظار برای پایان یافتن یک رشته دیگر است.public static void main(String []args) throws InterruptedException {
Runnable task = () -> {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
System.out.println("Interrupted");
}
};
Thread thread = new Thread(task);
thread.start();
thread.join();
System.out.println("Finished");
}
در این مثال، موضوع جدید 5 ثانیه میخوابد. در عین حال نخ اصلی منتظر می ماند تا نخ خواب بیدار شود و کار خود را تمام کند. اگر به وضعیت thread در JVisualVM نگاه کنید، به این صورت خواهد بود: 
join
روش بسیار ساده است، زیرا فقط یک متد با کد جاوا است که wait()
تا زمانی که رشته ای که روی آن فراخوانی می شود زنده است اجرا می شود. به محض از بین رفتن نخ (زمانی که کارش تمام شد) انتظار قطع می شود. و این همه جادوی روش است join()
. بنابراین، بیایید به جالب ترین چیز برویم.
نظارت کنید
Multithreading شامل مفهوم مانیتور می شود. کلمه مانیتور از طریق زبان لاتین قرن شانزدهم به انگلیسی آمده است و به معنای "ابزار یا وسیله ای است که برای مشاهده، بررسی یا ثبت یک پرونده مداوم از یک فرآیند استفاده می شود". در چارچوب این مقاله سعی خواهیم کرد به اصول اولیه بپردازیم. برای هر کسی که جزئیات را می خواهد، لطفاً به مطالب مرتبط شیرجه بزنید. ما سفر خود را با مشخصات زبان جاوا (JLS) آغاز می کنیم: 17.1. هماهنگ سازی . این می گوید:
lock()
یا رها کنند unlock()
. در مرحله بعد، آموزش را در وبسایت اوراکل خواهیم یافت: قفلهای درونی و همگامسازی
. این آموزش می گوید که همگام سازی جاوا حول یک موجودیت داخلی به نام قفل ذاتی یا قفل مانیتور ساخته شده است . این قفل اغلب به سادگی " مانیتور " نامیده می شود. همچنین می بینیم که هر شی در جاوا دارای یک قفل ذاتی مرتبط با آن است. می توانید Java - Intrinsic Locks and Synchronization را
بخوانید . در مرحله بعد مهم است که بفهمیم چگونه یک شی در جاوا می تواند با یک مانیتور مرتبط شود. در جاوا، هر شیء دارای یک هدر است که ابرداده داخلی را که از طریق کد در دسترس برنامه نویس نیست، ذخیره می کند، اما ماشین مجازی برای کار صحیح با اشیا به آن نیاز دارد. هدر شی شامل یک کلمه علامت گذاری است که به شکل زیر است: 
https://edu.netbeans.org/contrib/slides/java-overview-and-java-se6.pdf
public class HelloWorld{
public static void main(String []args){
Object object = new Object();
synchronized(object) {
System.out.println("Hello World");
}
}
}
در اینجا، رشته فعلی (رشتهای که این خطوط کد روی آن اجرا میشوند) از کلمه synchronized
کلیدی برای تلاش برای استفاده از مانیتور مرتبط با object"\
متغیر برای دریافت/دستیابی به قفل استفاده میکند. اگر هیچ کس دیگری برای مانیتور رقابت نمی کند (یعنی هیچ کس دیگری با استفاده از همان شی کد همگام سازی شده را اجرا نمی کند)، جاوا ممکن است سعی کند یک بهینه سازی به نام "قفل بایاس" را انجام دهد. یک برچسب مرتبط و یک رکورد در مورد اینکه کدام رشته دارای قفل مانیتور است به کلمه علامت در هدر شی اضافه می شود. این امر باعث کاهش سربار مورد نیاز برای قفل کردن مانیتور می شود. اگر مانیتور قبلاً متعلق به رشته دیگری بود، چنین قفلی کافی نیست. JVM به نوع بعدی قفل سوئیچ میکند: "قفل کردن پایه". از عملیات مقایسه و تعویض (CAS) استفاده می کند. علاوه بر این، خود کلمه علامت سربرگ شی دیگر کلمه علامت را ذخیره نمی کند، بلکه ارجاعی به محل ذخیره آن است، و تگ تغییر می کند تا JVM بفهمد که ما از قفل اولیه استفاده می کنیم. اگر چندین رشته برای یک مانیتور با هم رقابت کنند (یکی قفل را به دست آورده است، و دومی منتظر آزاد شدن قفل باشد)، تگ در کلمه علامت تغییر می کند و کلمه علامت اکنون ارجاع به مانیتور را ذخیره می کند. به عنوان یک شی - یک موجودیت داخلی JVM. همانطور که در JDK Enchancement Proposal (JEP) گفته شد، این وضعیت به فضایی در ناحیه Native Heap حافظه نیاز دارد تا این موجودیت ذخیره شود. ارجاع به محل حافظه این موجودیت داخلی در کلمه علامت سربرگ شی ذخیره می شود. بنابراین، یک مانیتور در واقع مکانیزمی برای همگام سازی دسترسی به منابع مشترک در بین موضوعات متعدد است. JVM بین چندین پیاده سازی این مکانیسم سوئیچ می کند. بنابراین، برای سادگی، وقتی در مورد مانیتور صحبت می کنیم، در واقع در مورد قفل صحبت می کنیم. 
همگام شده (در انتظار قفل)
همانطور که قبلا دیدیم، مفهوم "بلوک همگام" (یا "بخش بحرانی") ارتباط نزدیکی با مفهوم نمایشگر دارد. به یک مثال نگاه کنید:public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
Runnable task = () -> {
synchronized(lock) {
System.out.println("thread");
}
};
Thread th1 = new Thread(task);
th1.start();
synchronized(lock) {
for (int i = 0; i < 8; i++) {
Thread.currentThread().sleep(1000);
System.out.print(" " + i);
}
System.out.println(" ...");
}
}
در اینجا thread اصلی ابتدا شی وظیفه را به thread جدید منتقل می کند و سپس بلافاصله قفل را می گیرد و یک عملیات طولانی با آن انجام می دهد (8 ثانیه). در تمام این مدت، کار نمی تواند ادامه یابد، زیرا نمی تواند وارد بلوک شود synchronized
، زیرا قفل قبلاً بدست آمده است. اگر نخ نتواند قفل را بگیرد، منتظر مانیتور می ماند. به محض اینکه قفل را دریافت کرد، اجرا را ادامه خواهد داد. هنگامی که یک نخ از یک مانیتور خارج می شود، قفل را آزاد می کند. در JVisualVM، به این صورت است: 
th1.getState()
برمیگردد ، زیرا تا زمانی که حلقه در حال اجرا است، مانیتور شی توسط thread اشغال میشود و thread مسدود میشود و تا زمانی که قفل آزاد نشود نمیتواند ادامه یابد. علاوه بر بلوک های همگام، یک روش کامل را می توان همگام کرد. به عنوان مثال، در اینجا یک متد از کلاس آمده است: lock
main
th1
HashTable
public synchronized int size() {
return count;
}
این متد در هر لحظه فقط توسط یک رشته اجرا می شود. آیا واقعاً به قفل نیاز داریم؟ بله، ما به آن نیاز داریم. در مورد روش های نمونه، شی "این" (شیء فعلی) به عنوان یک قفل عمل می کند. بحث جالبی در مورد این موضوع در اینجا وجود دارد: آیا استفاده از روش همگام به جای بلوک همگام مزیتی دارد؟
. اگر متد ثابت باشد، قفل شیء "this" نخواهد بود (زیرا شیء "this" برای یک متد استاتیک وجود ندارد)، بلکه یک شی Class خواهد بود (مثلاً) Integer.class
.
صبر کنید (در انتظار یک مانیتور). متدهای notify() و notifyAll()
کلاس Thread روش انتظار دیگری دارد که با یک مانیتور مرتبط است. بر خلافsleep()
و join()
، این روش را نمی توان به سادگی فراخوانی کرد. نامش هست wait()
. متد wait
بر روی شیء مرتبط با مانیتور که می خواهیم منتظر آن باشیم فراخوانی می شود. بیایید یک مثال را ببینیم:
public static void main(String []args) throws InterruptedException {
Object lock = new Object();
// The task object will wait until it is notified via lock
Runnable task = () -> {
synchronized(lock) {
try {
lock.wait();
} catch(InterruptedException e) {
System.out.println("interrupted");
}
}
// After we are notified, we will wait until we can acquire the lock
System.out.println("thread");
};
Thread taskThread = new Thread(task);
taskThread.start();
// We sleep. Then we acquire the lock, notify, and release the lock
Thread.currentThread().sleep(3000);
System.out.println("main");
synchronized(lock) {
lock.notify();
}
}
در JVisualVM، به این صورت است: 
wait()
و notify()
متدها با java.lang.Object
. ممکن است عجیب به نظر برسد که متدهای مرتبط با رشته در Object
کلاس هستند. اما دلیل آن اکنون آشکار می شود. شما به یاد خواهید آورد که هر شی در جاوا یک هدر دارد. هدر حاوی اطلاعات مختلف خانه داری از جمله اطلاعات مربوط به مانیتور یعنی وضعیت قفل است. به یاد داشته باشید، هر شی یا نمونه ای از یک کلاس، با یک موجودیت داخلی در JVM، به نام قفل یا مانیتور ذاتی مرتبط است. در مثال بالا، کد مربوط به شیء وظیفه نشان می دهد که ما بلوک همگام سازی شده برای نمایشگر مرتبط با شی را وارد می کنیم lock
. اگر موفق شدیم قفل این مانیتور را بدست آوریم، wait()
نامیده می شود. رشته ای که وظیفه را اجرا می کند، lock
مانیتور شی را آزاد می کند، اما وارد صف رشته هایی می شود که منتظر اطلاع رسانی از lock
مانیتور شی هستند. این صف از رشته ها، مجموعه WAIT نامیده می شود که هدف آن را به درستی نشان می دهد. یعنی بیشتر یک مجموعه است تا یک صف. thread main
یک رشته جدید با شی task ایجاد می کند، آن را شروع می کند و 3 ثانیه صبر می کند. این باعث میشود که احتمال اینکه رشته جدید بتواند قفل قبل از main
نخ را بگیرد و وارد صف نمایشگر شود بسیار زیاد است. پس از آن، main
نخ خود وارد lock
بلوک همگام شیء می شود و با استفاده از مانیتور، اعلان رشته را انجام می دهد. پس از ارسال اعلان، main
thread مانیتور شی را آزاد می کند lock
و رشته جدید که قبلاً منتظر lock
رها شدن مانیتور شی بود، به اجرا ادامه می دهد. ارسال اعلان فقط به یک رشته ( notify()
) یا به طور همزمان به تمام رشته های موجود در صف ( notifyAll()
) امکان پذیر است. اینجا بیشتر بخوانید: تفاوت بین notify() و notifyAll() در جاوا
. توجه به این نکته مهم است که ترتیب اطلاع رسانی به نحوه پیاده سازی JVM بستگی دارد. اینجا بیشتر بخوانید: چگونه با notify و notifyAll گرسنگی را حل کنیم؟
. همگام سازی را می توان بدون تعیین یک شی انجام داد. شما می توانید این کار را زمانی انجام دهید که کل یک روش به جای یک بلوک کد، همگام سازی شده باشد. به عنوان مثال، برای متدهای استاتیک، قفل یک شی Class خواهد بود (از طریق .class
):
public static synchronized void printA() {
System.out.println("A");
}
public static void printB() {
synchronized(HelloWorld.class) {
System.out.println("B");
}
}
از نظر استفاده از قفل هر دو روش یکسان است. اگر روشی ثابت نباشد، همگام سازی با استفاده از جریان انجام می شود instance
، یعنی با استفاده از this
. ضمناً قبلاً گفتیم که می توانید از getState()
روش برای دریافت وضعیت یک موضوع استفاده کنید. به عنوان مثال، برای یک رشته در صف انتظار برای یک مانیتور، وضعیت WAITING یا TIMED_WAITING خواهد بود، در صورتی که روش wait()
یک بازه زمانی تعیین کرده باشد. 
https://stackoverflow.com/questions/36425942/what-is-the-lifecycle-of-thread-in-java
چرخه عمر نخ
در طول عمر آن، وضعیت یک رشته تغییر می کند. در واقع این تغییرات چرخه عمر نخ را تشکیل می دهند. به محض ایجاد یک موضوع، وضعیت آن NEW است. در این حالت، موضوع جدید هنوز در حال اجرا نیست و زمانبندی رشته جاوا هنوز چیزی در مورد آن نمی داند. برای اینکه زمانبندی رشته در مورد موضوع اطلاعات کسب کند، بایدthread.start()
متد را فراخوانی کنید. سپس thread به حالت RUNNABLE منتقل می شود. اینترنت نمودارهای نادرست زیادی دارد که بین حالت های "قابل اجرا" و "در حال اجرا" تمایز قائل می شود. اما این یک اشتباه است، زیرا جاوا بین "آماده کار" (قابل اجرا) و "کار" (در حال اجرا) تفاوتی قائل نمی شود. وقتی رشته ای زنده است اما فعال نیست (قابل اجرا نیست)، در یکی از دو حالت است:
- BLOCKED - در انتظار ورود به بخش مهم، یعنی یک
synchronized
بلوک. - WAITING - انتظار برای یک رشته دیگر برای برآوردن برخی شرایط.
getState()
روش استفاده کنید. Thread ها همچنین دارای isAlive()
متدی هستند که اگر موضوع TERMINATED نباشد مقدار true را برمی گرداند.
LockSupport و پارکینگ نخ
با شروع جاوا 1.6، مکانیزم جالبی به نام LockSupport ظاهر شد.
park()
متد بلافاصله برمیگردد و مجوز را در فرآیند مصرف میکند. در غیر این صورت مسدود می شود. فراخوانی unpark
روش باعث می شود مجوز در صورتی که هنوز در دسترس نباشد در دسترس باشد. فقط 1 مجوز وجود دارد. مستندات جاوا برای LockSupport
به Semaphore
کلاس اشاره دارد. بیایید به یک مثال ساده نگاه کنیم:
import java.util.concurrent.Semaphore;
public class HelloWorldApp{
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(0);
try {
semaphore.acquire();
} catch (InterruptedException e) {
// Request the permit and wait until we get it
e.printStackTrace();
}
System.out.println("Hello, World!");
}
}
این کد همیشه منتظر خواهد ماند، زیرا اکنون سمافور 0 مجوز دارد. و هنگامی که acquire()
در کد فراخوانی می شود (یعنی درخواست مجوز)، موضوع منتظر می ماند تا مجوز را دریافت کند. از آنجایی که منتظریم، باید رسیدگی کنیم InterruptedException
. جالب اینجاست که سمافور حالت نخ جداگانه ای پیدا می کند. اگر به JVisualVM نگاه کنیم، می بینیم که حالت "Wait" نیست، بلکه "Park" است. 
public static void main(String[] args) throws InterruptedException {
Runnable task = () -> {
// Park the current thread
System.err.println("Will be Parked");
LockSupport.park();
// As soon as we are unparked, we will start to act
System.err.println("Unparked");
};
Thread th = new Thread(task);
th.start();
Thread.currentThread().sleep(2000);
System.err.println("Thread state: " + th.getState());
LockSupport.unpark(th);
Thread.currentThread().sleep(2000);
}
وضعیت رشته WAITING خواهد بود، اما JVisualVM بین wait
کلمه synchronized
کلیدی و park
کلاس تمایز قائل می شود LockSupport
. چرا این موضوع LockSupport
اینقدر مهم است؟ ما دوباره به مستندات جاوا می پردازیم و به وضعیت رشته WAITING
نگاه می کنیم . همانطور که می بینید، تنها سه راه برای ورود به آن وجود دارد. دو تا از آن راه ها wait()
و join()
. و سومی این است LockSupport
. در جاوا، قفل ها همچنین می توانند بر روی t ساخته شوند LockSuppor
و ابزارهای سطح بالاتری را ارائه دهند. بیایید سعی کنیم از یکی استفاده کنیم. به عنوان مثال، نگاهی به ReentrantLock
:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class HelloWorld{
public static void main(String []args) throws InterruptedException {
Lock lock = new ReentrantLock();
Runnable task = () -> {
lock.lock();
System.out.println("Thread");
lock.unlock();
};
lock.lock();
Thread th = new Thread(task);
th.start();
System.out.println("main");
Thread.currentThread().sleep(2000);
lock.unlock();
}
}
درست مانند نمونه های قبلی، همه چیز در اینجا ساده است. شی lock
منتظر می ماند تا کسی منبع مشترک را آزاد کند. اگر به JVisualVM نگاه کنیم، خواهیم دید که رشته جدید تا زمانی که thread main
قفل آن را آزاد نکند، پارک خواهد شد. می توانید اطلاعات بیشتری درباره قفل ها در اینجا بخوانید: Java 8 StampedLocks در مقابل ReadWriteLocks و Synchronized
and Lock API در جاوا. برای درک بهتر نحوه پیادهسازی قفلها، خواندن در مورد Phaser در این مقاله مفید است: راهنمای Java Phaser
. و در مورد همگامکنندههای مختلف، باید مقاله
DZone در مورد همگامسازهای جاوا را بخوانید.
نتیجه
در این بررسی، روشهای اصلی تعامل رشتهها در جاوا را بررسی کردیم. مواد اضافی:- بهتر با هم: جاوا و کلاس Thread. بخش اول - موضوعات اجرا
- https://dzone.com/articles/the-java-synchronizers
- https://www.javatpoint.com/java-multithreading-interview-questions
GO TO FULL VERSION