معرفی
تاپیک ها چیز جالبی هستند. در بررسیهای گذشته، ما به برخی از ابزارهای موجود برای اجرای multithreading نگاه کردیم. بیایید ببینیم چه کارهای جالب دیگری می توانیم انجام دهیم. در این مرحله، ما چیزهای زیادی می دانیم. به عنوان مثال، از "
بهتر با هم: جاوا و کلاس Thread. قسمت اول - Threads of execution
"، می دانیم که کلاس Thread یک رشته اجرا را نشان می دهد. ما می دانیم که یک رشته وظایفی را انجام می دهد. اگر میخواهیم وظایفمان قادر به انجام آن باشند
run
، باید موضوع را با علامت گذاری کنیم
Runnable
.
برای یادآوری، می توانیم از
کامپایلر جاوا آنلاین Tutorialspoint
استفاده کنیم :
public static void main(String[] args){
Runnable task = () -> {
Thread thread = Thread.currentThread();
System.out.println("Hello from " + thread.getName());
};
Thread thread = new Thread(task);
thread.start();
}
ما همچنین می دانیم که چیزی به نام قفل داریم. ما در مورد این موضوع در "
بهتر با هم: کلاس جاوا و موضوع. قسمت دوم - همگام سازی"
یاد گرفتیم. اگر یک رشته یک قفل دریافت کند، رشته دیگری که تلاش می کند قفل را بدست آورد مجبور می شود منتظر آزاد شدن قفل بماند:
import java.util.concurrent.locks.*;
public class HelloWorld{
public static void main(String []args){
Lock lock = new ReentrantLock();
Runnable task = () -> {
lock.lock();
Thread thread = Thread.currentThread();
System.out.println("Hello from " + thread.getName());
lock.unlock();
};
Thread thread = new Thread(task);
thread.start();
}
}
فکر می کنم وقت آن رسیده است که در مورد کارهای جالب دیگری که می توانیم انجام دهیم صحبت کنیم.
سمافورها
ساده ترین راه برای کنترل تعداد نخ هایی که می توانند به طور همزمان اجرا شوند، سمافور است. مثل سیگنال راه آهن است. سبز به معنی ادامه دادن است. قرمز به معنای صبر کردن است. منتظر چه چیزی از سمافور باشید؟ دسترسی داشته باشید. برای دسترسی، باید آن را بدست آوریم. و زمانی که دیگر نیازی به دسترسی نیست، باید آن را واگذار کرده یا آزاد کنیم. بیایید ببینیم این چگونه کار می کند. باید
java.util.concurrent.Semaphore
کلاس را وارد کنیم. مثال:
public static void main(String[] args) throws InterruptedException {
Semaphore semaphore = new Semaphore(0);
Runnable task = () -> {
try {
semaphore.acquire();
System.out.println("Finished");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
new Thread(task).start();
Thread.sleep(5000);
semaphore.release(1);
}
همانطور که می بینید، این عملیات (اکتساب و انتشار) به ما کمک می کند تا بفهمیم یک سمافور چگونه کار می کند. مهمترین چیز این است که اگر قرار است دسترسی پیدا کنیم، سمافور باید مجوزهای مثبت داشته باشد. این تعداد را می توان به یک عدد منفی مقدار دهی اولیه کرد. و ما می توانیم بیش از 1 مجوز درخواست (کسب) کنیم.
CountDown Latch
مکانیسم بعدی این است
CountDownLatch
. جای تعجب نیست که این یک چفت با شمارش معکوس است. در اینجا به دستور import مناسب برای کلاس نیاز داریم
java.util.concurrent.CountDownLatch
. مثل یک مسابقه پیاده روی است که همه در خط شروع جمع می شوند. و هنگامی که همه آماده شدند، همه سیگنال شروع را به طور همزمان دریافت می کنند و همزمان شروع می کنند. مثال:
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(3);
Runnable task = () -> {
try {
countDownLatch.countDown();
System.out.println("Countdown: " + countDownLatch.getCount());
countDownLatch.await();
System.out.println("Finished");
} catch (InterruptedException e) {
e.printStackTrace();
}
};
for (int i = 0; i < 3; i++) {
new Thread(task).start();
}
}
ابتدا به قفل می گوییم
countDown()
. گوگل شمارش معکوس را به عنوان "عمل شمارش اعداد به ترتیب معکوس به صفر" تعریف می کند. و سپس به لچ می گوییم
await()
، یعنی صبر کنید تا شمارنده صفر شود. جالب اینجاست که این یک شمارنده یکبار مصرف است. اسناد جاوا میگوید: «وقتی رشتهها باید بهطور مکرر به این روش شمارش معکوس کنند، در عوض از CyclicBarrier استفاده کنید». به عبارت دیگر، اگر به یک شمارنده قابل استفاده مجدد نیاز دارید، به گزینه دیگری نیاز دارید:
CyclicBarrier
.
Cyclic Barrier
همانطور که از نام آن پیداست،
CyclicBarrier
یک مانع "قابل استفاده مجدد" است. ما باید
java.util.concurrent.CyclicBarrier
کلاس را وارد کنیم. بیایید به یک مثال نگاه کنیم:
public static void main(String[] args) throws InterruptedException {
Runnable action = () -> System.out.println("On your mark!");
CyclicBarrier barrier = new CyclicBarrier(3, action);
Runnable task = () -> {
try {
barrier.await();
System.out.println("Finished");
} catch (BrokenBarrierException | InterruptedException e) {
e.printStackTrace();
}
};
System.out.println("Limit: " + barrier.getParties());
for (int i = 0; i < 3; i++) {
new Thread(task).start();
}
}
همانطور که می بینید، thread
await
متد را اجرا می کند، یعنی منتظر می ماند. در این حالت مقدار مانع کاهش می یابد.
barrier.isBroken()
وقتی شمارش معکوس به صفر برسد، مانع شکسته ( ) در نظر گرفته می شود . برای تنظیم مجدد مانع، باید
reset()
متدی را فراخوانی کنید که
CountDownLatch
ندارد.
مبدل
مکانیسم بعدی Exchanger است. در این زمینه، Exchange یک نقطه همگامسازی است که در آن چیزها تغییر میکنند یا مبادله میشوند. همانطور که انتظار دارید، an
Exchanger
کلاسی است که تبادل یا مبادله را انجام می دهد. بیایید ساده ترین مثال را بررسی کنیم:
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<>();
Runnable task = () -> {
try {
Thread thread = Thread.currentThread();
String withThreadName = exchanger.exchange(thread.getName());
System.out.println(thread.getName() + " exchanged with " + withThreadName);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
new Thread(task).start();
new Thread(task).start();
}
در اینجا دو موضوع را شروع می کنیم. هر کدام از آنها متد تبادل را اجرا می کنند و منتظر می مانند تا رشته دیگر نیز متد تبادل را اجرا کند. در انجام این کار، نخ ها آرگومان های پاس شده را مبادله می کنند. جالب هست. شما را به یاد چیزی نمی اندازد؟ یادآور
SynchronousQueue
, که در قلب نهفته است
CachedThreadPool
. برای وضوح، در اینجا یک مثال است:
public static void main(String[] args) throws InterruptedException {
SynchronousQueue<String> queue = new SynchronousQueue<>();
Runnable task = () -> {
try {
System.out.println(queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
};
new Thread(task).start();
queue.put("Message");
}
مثال نشان می دهد که وقتی یک رشته جدید شروع می شود، منتظر می ماند، زیرا صف خالی خواهد بود. و سپس رشته اصلی رشته "پیام" را در صف قرار می دهد. علاوه بر این، تا زمانی که این رشته از صف دریافت نشود، متوقف خواهد شد. همچنین میتوانید «
SynchronousQueue vs Exchanger
» را بخوانید تا اطلاعات بیشتری در مورد این موضوع پیدا کنید.
فازر
ما بهترین ها را برای آخرین بار ذخیره کرده ایم -
Phaser
. ما باید
java.util.concurrent.Phaser
کلاس را وارد کنیم. بیایید به یک مثال ساده نگاه کنیم:
public static void main(String[] args) throws InterruptedException {
Phaser phaser = new Phaser();
phaser.register();
System.out.println("Phasecount is " + phaser.getPhase());
testPhaser(phaser);
testPhaser(phaser);
testPhaser(phaser);
Thread.sleep(3000);
phaser.arriveAndDeregister();
System.out.println("Phasecount is " + phaser.getPhase());
}
private static void testPhaser(final Phaser phaser) {
phaser.register();
new Thread(() -> {
String name = Thread.currentThread().getName();
System.out.println(name + " arrived");
phaser.arriveAndAwaitAdvance();
System.out.println(name + " after passing barrier");
}).start();
}
این مثال نشان می دهد که هنگام استفاده از
Phaser
, زمانی که تعداد ثبت نام ها با تعداد ورود به مانع مطابقت داشته باشد، مانع شکسته می شود.
Phaser
با مطالعه
این مقاله GeeksforGeeks
می توانید بیشتر با آن آشنا شوید .
خلاصه
همانطور که از این مثال ها می بینید، روش های مختلفی برای همگام سازی نخ ها وجود دارد. پیش از این، سعی کردم جنبه های چند رشته ای را به خاطر بیاورم. امیدوارم قسمت های قبلی این مجموعه مفید بوده باشند. برخی می گویند مسیر چند رشته ای با کتاب «همزمانی جاوا در عمل» آغاز می شود. اگرچه این کتاب در سال 2006 منتشر شد، اما مردم می گویند که این کتاب کاملاً اساسی است و هنوز هم امروزی مرتبط است. به عنوان مثال، می توانید بحث را در اینجا بخوانید:
آیا "همگامی جاوا در عمل" هنوز معتبر است؟
. خواندن پیوندهای موجود در بحث نیز مفید است. به عنوان مثال، پیوندی به کتاب
The Well-Grounded Java Developer
وجود دارد ، و ما به طور خاص به
فصل 4 اشاره خواهیم کرد. همزمانی مدرن
. همچنین یک بررسی کامل در مورد این موضوع وجود دارد:
آیا "همراهی جاوا در عمل" هنوز در عصر جاوا 8 معتبر است؟
آن مقاله همچنین پیشنهاداتی در مورد مطالب دیگری برای خواندن این موضوع ارائه می دهد.
پس از آن، می توانید به یک کتاب عالی مانند OCA/OCP Java SE 8 Programmer Practice Tests
نگاه کنید . ما به مخفف دوم علاقه مندیم: OCP (Oracle Certified Professional). آزمونها را در «فصل 20: همزمانی جاوا» خواهید یافت. این کتاب دارای پرسش و پاسخ همراه با توضیحات است. به عنوان مثال:
بسیاری از افراد ممکن است شروع به گفتن کنند که این سوال نمونه دیگری از حفظ روش ها است. از یک طرف بله. از سوی دیگر، میتوانید با یادآوری اینکه
ExecutorService
نوعی «ارتقا» از
Executor
. و
Executor
قصد دارد به سادگی نحوه ایجاد رشته ها را پنهان کند، اما راه اصلی برای اجرای آنها نیست، یعنی شروع یک
Runnable
شی در یک رشته جدید. به همین دلیل است که وجود ندارد
execute(Callable)
- زیرا در
ExecutorService
، the
Executor
به سادگی
submit()
متدهایی را اضافه می کند که می توانند یک شی را برگردانند
Future
. البته، ما میتوانیم فهرستی از روشها را به خاطر بسپاریم، اما بسیار سادهتر است که پاسخ خود را بر اساس دانش خود از ماهیت کلاسها ارائه کنیم. و در اینجا برخی از مطالب اضافی در مورد این موضوع وجود دارد:
بهتر با هم: جاوا و کلاس Thread. قسمت اول - موضوعات اجرا
بهتر با هم: جاوا و کلاس Thread. بخش دوم - همگام سازی
بهتر با هم: کلاس جاوا و Thread. بخش سوم - تعامل
بهتر با هم: کلاس جاوا و Thread. بخش چهارم - Callable، Future، و دوستان
بهتر با هم: Java and the Thread کلاس. قسمت پنجم - مجری، ThreadPool، Fork/Join
GO TO FULL VERSION