برنامه نویسی 195 بازدید

در این مطلب به بررسی <java.util.concurrent.Exchanger<T خواهیم پرداخت. این ساختار به منزله نقطه مشترکی بین دو نخ (Thread) ‌جاوا برای مبادله اشیا بین آن‌ها عمل می‌کند. برای آشنایی با Exchanger در جاوا تا انتهای این راهنما با ما همراه باشید.

آشنایی با Exchanger در جاوا

کلاس Exchanger در جاوا می‌تواند برای اشترک اشیا بین دو نخ از نوع T استفاده شود. این کلاس تنها یک متد منفرد overload-شده به نام exchange(T t) ارائه می‌کند. زمانی که این متد فراخوانی شود، منتظر نخ دیگر می‌ماند تا آن نیز فراخوانی شود. در این نقطه نخ دوم متوجه می‌شود که نخ اول با شیء آن منتظر مانده است. این نخ اشیایی را که نگهداری می‌کند، مبادله کرده و سیگنالی برای مبادله ارسال می‌کند تا بتوانند بازگشت یابند.

در ادامه مثالی از این موضوع را مشاهده می‌کنید که به درک بهتر کاربرد پیام مبادله بین دو نخ با استفاده از Exchanger کمک می‌کند:

@Test
public void givenThreads_whenMessageExchanged_thenCorrect() {
    Exchanger<String> exchanger = new Exchanger<>();
 
    Runnable taskA = () -> {
        try {
            String message = exchanger.exchange("from A");
            assertEquals("from B", message);
        } catch (InterruptedException e) {
            Thread.currentThread.interrupt();
            throw new RuntimeException(e);
        }
    };
 
    Runnable taskB = () -> {
        try {
            String message = exchanger.exchange("from B");
            assertEquals("from A", message);
        } catch (InterruptedException e) {
            Thread.currentThread.interrupt();
            throw new RuntimeException(e);
        }
    };
    CompletableFuture.allOf(
      runAsync(taskA), runAsync(taskB)).join();
}

در مثال فوق، دو نخ داریم که پیام‌ها را بین همدیگر با استفاده از exchanger مشترک مبادله می‌کنند. در ادامه مثال دیگری را می‌بینید که در آن یک شیء را از نخ اصلی با یک نخ جدید مبادله می‌کنیم:

@Test
public void givenThread_WhenExchangedMessage_thenCorrect() throws InterruptedException {
    Exchanger<String> exchanger = new Exchanger<>();
 
    Runnable runner = () -> {
        try {
            String message = exchanger.exchange("from runner");
            assertEquals("to runner", message);
        } catch (InterruptedException e) {
            Thread.currentThread.interrupt();
            throw new RuntimeException(e);
        }
    };
    CompletableFuture<Void> result 
      = CompletableFuture.runAsync(runner);
    String msg = exchanger.exchange("to runner");
    assertEquals("from runner", msg);
    result.join();
}

توجه کنید که ابتدا باید نخ runner را اجرا کنیم و سپس ()exchange را در نخ اصلی فراخوانی نماییم.

همچنین توجه کنید در صورتی که نخ دوم در زمان مورد نظر به نقطه تبادل نرسد، فراخوانی نخ اول ممکن است timeout شود. این که نخ اول باید چه قدر صبر کند، با استفاده از متد overload-شده exchange(T t, long timeout, TimeUnit timeUnit) تنظیم می‌شود.

آشنایی با Exchanger در جاوا

مبادله داده بدون GC

Exchanger می‌تواند برای ایجاد الگوهای شبیه pipeline با ارسال داده از یک نخ به نخ دیگر استفاده شود. در این بخش یک پشته ساده از نخ‌ها ایجاد می‌کنیم که به صورت پیوسته داده‌ها را بین همدیگر به صورت یک pipeline ارسال می‌کنند.

@Test
public void givenData_whenPassedThrough_thenCorrect() throws InterruptedException {
 
    Exchanger<Queue<String>> readerExchanger = new Exchanger<>();
    Exchanger<Queue<String>> writerExchanger = new Exchanger<>();
 
    Runnable reader = () -> {
        Queue<String> readerBuffer = new ConcurrentLinkedQueue<>();
        while (true) {
            readerBuffer.add(UUID.randomUUID().toString());
            if (readerBuffer.size() >= BUFFER_SIZE) {
                readerBuffer = readerExchanger.exchange(readerBuffer);
            }
        }
    };
 
    Runnable processor = () -> {
        Queue<String> processorBuffer = new ConcurrentLinkedQueue<>();
        Queue<String> writerBuffer = new ConcurrentLinkedQueue<>();
        processorBuffer = readerExchanger.exchange(processorBuffer);
        while (true) {
            writerBuffer.add(processorBuffer.poll());
            if (processorBuffer.isEmpty()) {
                processorBuffer = readerExchanger.exchange(processorBuffer);
                writerBuffer = writerExchanger.exchange(writerBuffer);
            }
        }
    };
 
    Runnable writer = () -> {
        Queue<String> writerBuffer = new ConcurrentLinkedQueue<>();
        writerBuffer = writerExchanger.exchange(writerBuffer);
        while (true) {
            System.out.println(writerBuffer.poll());
            if (writerBuffer.isEmpty()) {
                writerBuffer = writerExchanger.exchange(writerBuffer);
            }
        }
    };
    CompletableFuture.allOf(
      runAsync(reader), 
      runAsync(processor),
      runAsync(writer)).join();
}

در مثال فوق، سه نخ به نام‌های reader، processor و writer داریم. این سه نخ در مجموع به صورت یک pipeline منفرد عمل می‌کند که داده‌ها را بین خود مبادله می‌کنند. readerExchanger بین نخ‌های reader و processor مشترک است، در حالی که writerExchanger بین نخ‌های processor و writer به اشتراک درآمده است.

توجه کنید که این مثال، تنها به منظور تفهیم مطلب ارائه شده است. در عمل باید در مورد ایجاد حلقه‌های نامتناهی با while(true) مراقب باشیم. همچنین برای این که کد خوانایی بنویسیم، از مدیریت برخی استثناها خودداری کرده‌ایم. این الگو برای مبادله داده‌ها در عین استفاده مجدد از بافر به ما امکان می‌دهد که Garbage Collection کمتری داشته باشیم. متد exchange وهله‌های صف یکسانی را بازگشت می‌دهد و از این رو می‌توان برای این اشیا از GC استفاده نکرد. برخلاف دیگر صف‌های مسدودسازی، exchanger هیچ گروه یا شیئی برای نگهداری و اشتراک داده‌ها ایجاد نمی‌کند.

ایجاد یک چنین pipeline مشابه الگوی Disrupter است و تنها یک تفاوت کوچک به این صورت دارد که الگوی Disrupter از چند تولیدکننده و مصرف‌کننده پشتیبانی می‌کند، در حالی که exchanger می‌تواند بین یک جفت از تولیدکننده‌ها و مصرف‌کننده‌ها مورد استفاده قرار گیرد.

سخن پایانی

در این مقاله با <Exchanger<T در جاوا آشنا شدیم و طرز کار آن را شناختیم و شیوه استفاده از کلاس Exchanger را نیز در عمل مشاهده کردیم. همچنین یک pipeline ساخته و مبادله داده بدون GC را بین نخ‌ها آزمودیم.

اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.

اگر این مطلب برای شما مفید بوده است، آموزش‌ها و مطالب زیر نیز به شما پیشنهاد می‌شوند:

«میثم لطفی» دانش‌آموخته ریاضیات و شیفته فناوری به خصوص در حوزه رایانه است. وی در حال حاضر علاوه بر پیگیری علاقه‌مندی‌هایش در رشته‌های برنامه‌نویسی، کپی‌رایتینگ و محتوای چندرسانه‌ای، در زمینه نگارش مقالاتی با محوریت نرم‌افزار نیز با مجله فرادرس همکاری دارد.

آیا این مطلب برای شما مفید بود؟

نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *