CodeGym /وبلاگ جاوا /Random-FA /بهتر با هم: جاوا و کلاس Thread. بخش چهارم - تماس‌پذیر، آی...
John Squirrels
مرحله
San Francisco

بهتر با هم: جاوا و کلاس Thread. بخش چهارم - تماس‌پذیر، آینده و دوستان

در گروه منتشر شد

معرفی

در قسمت اول ، نحوه ایجاد موضوعات را بررسی کردیم. بیایید یک بار دیگر به یاد بیاوریم. بهتر با هم: جاوا و کلاس Thread.  قسمت چهارم - تماس پذیر، آینده و دوستان - 1یک رشته با کلاس Thread نمایش داده می شود که run()متد آن فراخوانی می شود. پس بیایید از کامپایلر جاوا آنلاین Tutorialspoint استفاده کنیم و کد زیر را اجرا کنیم:
public class HelloWorld {

    public static void main(String[] args) {
        Runnable task = () -> {
            System.out.println("Hello World");
        };
        new Thread(task).start();
    }
}
آیا این تنها گزینه برای شروع یک کار در یک موضوع است؟

java.util.concurrent.Callable

معلوم شد که java.lang.Runnable برادری به نام java.util.concurrent.Callable دارد که با جاوا 1.5 وارد دنیا شده است. چه تفاوت هایی دارند؟ اگر به Javadoc برای این رابط دقت کنید، می بینیم که برخلاف Runnable, رابط جدید call()متدی را اعلام می کند که نتیجه را برمی گرداند. همچنین، Exception را به طور پیش فرض می اندازد. یعنی ما را از try-catchبلاک کردن استثناهای بررسی شده نجات می دهد. بد نیست، درست است؟ اکنون به جای این کار یک وظیفه جدید داریم Runnable:
Callable task = () -> {
	return "Hello, World!";
};
اما با آن چه کنیم؟ چرا به وظیفه ای نیاز داریم که روی رشته ای اجرا شود که نتیجه را برمی گرداند؟ بدیهی است که برای هر اقدامی که در آینده انجام شود، انتظار داریم نتیجه آن اقدامات را در آینده دریافت کنیم. و ما یک رابط با نام مربوطه داریم:java.util.concurrent.Future

java.util.concurrent.Future

رابط java.util.concurrent.Future یک API را برای کار با وظایفی تعریف می کند که نتایج آنها را در آینده دریافت می کنیم: روش هایی برای به دست آوردن نتیجه و روش هایی برای بررسی وضعیت. در رابطه با Future، ما علاقه مند به پیاده سازی آن در کلاس java.util.concurrent.FutureTask هستیم . این "وظیفه" است که در اجرا خواهد شد Future. چیزی که این پیاده سازی را جالب تر می کند این است که Runnable را نیز پیاده سازی می کند. شما می توانید این را نوعی آداپتور بین مدل قدیمی کار با وظایف روی رشته ها و مدل جدید (به معنای جدید در جاوا 1.5) در نظر بگیرید. به عنوان مثال:
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class HelloWorld {

    public static void main(String[] args) throws Exception {
        Callable task = () -> {
            return "Hello, World!";
        };
        FutureTask<String> future = new FutureTask<>(task);
        new Thread(future).start();
        System.out.println(future.get());
    }
}
همانطور که از مثال می بینید، ما از getروش برای به دست آوردن نتیجه از کار استفاده می کنیم. توجه داشته باشید:وقتی نتیجه را با استفاده از get()روش بدست آورید، اجرا همزمان می شود! فکر می کنید چه مکانیزمی در اینجا استفاده خواهد شد؟ درست است، هیچ بلوک همگام سازی وجود ندارد. به همین دلیل است که ما WAITING را در JVisualVM به‌عنوان a monitorیا نمی‌بینیم wait، بلکه به‌عنوان park()روش آشنا (زیرا LockSupportمکانیزم در حال استفاده است).

رابط های کاربردی

در مرحله بعد، ما در مورد کلاس های جاوا 1.8 صحبت خواهیم کرد، بنابراین بهتر است یک مقدمه کوتاه ارائه دهیم. به کد زیر نگاه کنید:
Supplier<String> supplier = new Supplier<String>() {
	@Override
	public String get() {
		return "String";
	}
};
Consumer<String> consumer = new Consumer<String>() {
	@Override
	public void accept(String s) {
		System.out.println(s);
	}
};
Function<String, Integer> converter = new Function<String, Integer>() {
	@Override
	public Integer apply(String s) {
		return Integer.valueOf(s);
	}
};
تعداد زیادی کد اضافی، نمی گویید؟ هر یک از کلاس های اعلام شده یک تابع را انجام می دهد، اما ما از یک دسته کد پشتیبانی اضافی برای تعریف آن استفاده می کنیم. و توسعه دهندگان جاوا اینگونه فکر می کردند. بر این اساس، آنها مجموعه‌ای از «واسط‌های عملکردی» ( @FunctionalInterface) را معرفی کردند و تصمیم گرفتند که اکنون خود جاوا «تفکر» را انجام دهد، و فقط چیزهای مهم را برای ما باقی می‌گذارد که نگرانشان باشیم:
Supplier<String> supplier = () -> "String";
Consumer<String> consumer = s -> System.out.println(s);
Function<String, Integer> converter = s -> Integer.valueOf(s);
یک Supplierلوازم هیچ پارامتری ندارد، اما چیزی را برمی گرداند. به این ترتیب چیزها را تامین می کند. A Consumerمصرف می کند. چیزی را به عنوان ورودی (استدلال) می گیرد و با آن کاری انجام می دهد. بحث آن چیزی است که مصرف می کند. سپس ما نیز داریم Function. ورودی ها (استدلال ها) را می گیرد، کاری انجام می دهد و چیزی را برمی گرداند. می بینید که ما به طور فعال از ژنریک استفاده می کنیم. اگر مطمئن نیستید، می‌توانید با خواندن « عمومی در جاوا: نحوه استفاده از براکت‌های زاویه‌دار در عمل » تجدید نظر کنید.

CompletableFuture

زمان گذشت و کلاس جدیدی به نام CompletableFutureدر جاوا 1.8 ظاهر شد. این Futureرابط را پیاده سازی می کند، یعنی وظایف ما در آینده تکمیل می شود و می توانیم get()برای دریافت نتیجه تماس بگیریم. اما این CompletionStageرابط را نیز پیاده سازی می کند. این نام گویای همه چیز است: این مرحله مشخصی از برخی محاسبات است. مقدمه کوتاهی درباره موضوع را می توان در بررسی اینجا یافت: مقدمه CompletionStage و CompletableFuture. بریم سر اصل مطلب بیایید به لیستی از روش‌های استاتیک موجود که به ما در شروع کار کمک می‌کنند نگاهی بیندازیم: بهتر با هم: جاوا و کلاس Thread.  قسمت چهارم - تماس پذیر، آینده و دوستان - 2در اینجا گزینه‌هایی برای استفاده از آنها وجود دارد:
import java.util.concurrent.CompletableFuture;
public class App {
    public static void main(String[] args) throws Exception {
        // A CompletableFuture that already contains a Result
        CompletableFuture<String> completed;
        completed = CompletableFuture.completedFuture("Just a value");
        // A CompletableFuture that runs a new thread from Runnable. That's why it's Void
        CompletableFuture<Void> voidCompletableFuture;
        voidCompletableFuture = CompletableFuture.runAsync(() -> {
            System.out.println("run " + Thread.currentThread().getName());
        });
        // A CompletableFuture that starts a new thread whose result we'll get from a Supplier
        CompletableFuture<String> supplier;
        supplier = CompletableFuture.supplyAsync(() -> {
            System.out.println("supply " + Thread.currentThread().getName());
            return "Value";
        });
    }
}
اگر این کد را اجرا کنیم، خواهیم دید که ایجاد یک CompletableFutureشامل راه اندازی یک خط لوله کامل نیز می شود. بنابراین، با شباهت خاصی به SteamAPI از Java8، اینجاست که تفاوت بین این رویکردها را پیدا می کنیم. مثلا:
List<String> array = Arrays.asList("one", "two");
Stream<String> stringStream = array.stream().map(value -> {
	System.out.println("Executed");
	return value.toUpperCase();
});
این نمونه ای از استریم API جاوا 8 است. اگر این کد را اجرا کنید، خواهید دید که "Executed" نمایش داده نمی شود. به عبارت دیگر، هنگامی که یک جریان در جاوا ایجاد می شود، جریان بلافاصله شروع نمی شود. در عوض، منتظر می ماند تا کسی ارزشی از آن بخواهد. اما CompletableFutureبلافاصله شروع به اجرای خط لوله می کند، بدون اینکه منتظر کسی باشد که از آن مقدار بخواهد. من فکر می کنم درک این مهم است. S O، ما یک CompletableFuture. چگونه می توانیم خط لوله (یا زنجیره) بسازیم و چه مکانیزم هایی داریم؟ آن رابط های کاربردی را که قبلاً در مورد آنها نوشتیم به یاد بیاورید.
  • ما a داریم Functionکه یک A می گیرد و یک B برمی گرداند. یک متد دارد: apply().
  • ما a داریم Consumerکه یک A می گیرد و چیزی بر نمی گرداند (Void). یک روش دارد: accept().
  • ما داریم Runnableکه روی موضوع اجرا می شود و هیچ چیزی را نمی گیرد و چیزی را برمی گرداند. یک روش دارد: run().
نکته بعدی که باید به خاطر بسپارید این است که از , و در کار خود CompletableFutureاستفاده می کند . بر این اساس، همیشه می توانید بدانید که می توانید کارهای زیر را با : RunnableConsumersFunctionsCompletableFuture
public static void main(String[] args) throws Exception {
        AtomicLong longValue = new AtomicLong(0);
        Runnable task = () -> longValue.set(new Date().getTime());
        Function<Long, Date> dateConverter = (longvalue) -> new Date(longvalue);
        Consumer<Date> printer = date -> {
            System.out.println(date);
            System.out.flush();
        };
        // CompletableFuture computation
        CompletableFuture.runAsync(task)
                         .thenApply((v) -> longValue.get())
                         .thenApply(dateConverter)
                         .thenAccept(printer);
}
متدهای thenRun()، thenApply()و thenAccept()دارای نسخه‌های "Async" هستند. این بدان معنی است که این مراحل در یک موضوع متفاوت تکمیل می شود. این موضوع از یک استخر خاص گرفته خواهد شد - بنابراین ما از قبل نمی دانیم که آیا یک موضوع جدید یا قدیمی خواهد بود. همه چیز به این بستگی دارد که وظایف چقدر محاسباتی فشرده هستند. علاوه بر این روش ها، سه احتمال جالب دیگر نیز وجود دارد. برای وضوح، بیایید تصور کنیم که یک سرویس خاص داریم که نوعی پیام را از جایی دریافت می کند - و این زمان می برد:
public static class NewsService {
	public static String getMessage() {
		try {
			Thread.currentThread().sleep(3000);
			return "Message";
		} catch (InterruptedException e) {
			throw new IllegalStateException(e);
		}
	}
}
حال، بیایید نگاهی به توانایی های دیگری که CompletableFutureفراهم می کند بیاندازیم. ما می توانیم نتیجه a را CompletableFutureبا نتیجه دیگری ترکیب کنیم CompletableFuture:
Supplier newsSupplier = () -> NewsService.getMessage();

CompletableFuture<String> reader = CompletableFuture.supplyAsync(newsSupplier);
CompletableFuture.completedFuture("!!")
				 .thenCombine(reader, (a, b) -> b + a)
				 .thenAccept(result -> System.out.println(result))
				 .get();
توجه داشته باشید که thread ها به طور پیش فرض رشته های شبح هستند، بنابراین برای وضوح، ما get()منتظر نتیجه هستیم. نه تنها می توانیم ترکیب کنیم CompletableFutures، بلکه می توانیم یک CompletableFuture:
CompletableFuture.completedFuture(2L)
				.thenCompose((val) -> CompletableFuture.completedFuture(val + 2))
                               .thenAccept(result -> System.out.println(result));
در اینجا می خواهم توجه داشته باشم که از CompletableFuture.completedFuture()روش برای اختصار استفاده شد. این روش نخ جدیدی ایجاد نمی کند، بنابراین بقیه خط لوله در همان رشته ای که completedFutureفراخوانی شده بود اجرا می شود. یک روش هم وجود دارد thenAcceptBoth(). بسیار شبیه به است accept()، اما اگر thenAccept()a را بپذیرد Consumer، + thenAcceptBoth()دیگری را به عنوان ورودی می پذیرد، یعنی a که به جای یک منبع، 2 منبع می گیرد. قابلیت جالب دیگری نیز توسط متدهایی ارائه می‌شود که نام آن شامل کلمه "یا" می‌شود: این روش‌ها یک جایگزین را می‌پذیرند و روی آن که ابتدا اجرا می‌شود اجرا می‌شوند. در نهایت، می‌خواهم این بررسی را با یکی دیگر از ویژگی‌های جالب پایان دهم : مدیریت خطا. CompletableStageBiConsumerconsumerبهتر با هم: جاوا و کلاس Thread.  قسمت چهارم - تماس پذیر، آینده و دوستان - 3CompletableStageCompletableStageCompletableFuture
CompletableFuture.completedFuture(2L)
				 .thenApply((a) -> {
					throw new IllegalStateException("error");
				 }).thenApply((a) -> 3L)
				 //.exceptionally(ex -> 0L)
				 .thenAccept(val -> System.out.println(val));
این کد هیچ کاری انجام نمی دهد، زیرا یک استثنا وجود خواهد داشت و هیچ چیز دیگری اتفاق نمی افتد. اما با برداشتن اظهارنظر از عبارت " استثنایی " ، رفتار مورد انتظار را تعریف می کنیم. در مورد CompletableFuture, من همچنین توصیه می کنم ویدیو زیر را تماشا کنید: به نظر حقیر من اینها جزو توضیحی ترین ویدیوهای اینترنت هستند. آنها باید روشن کنند که همه اینها چگونه کار می کند، چه ابزاری در دسترس داریم و چرا همه اینها مورد نیاز است.

نتیجه

امیدواریم، اکنون مشخص شده است که چگونه می توانید از موضوعات برای دریافت محاسبات پس از تکمیل آنها استفاده کنید. مواد اضافی: بهتر با هم: جاوا و کلاس Thread. قسمت اول - موضوعات اجرا بهتر با هم: جاوا و کلاس Thread. بخش دوم - همگام سازی بهتر با هم: کلاس جاوا و Thread. بخش سوم - تعامل بهتر با هم: کلاس جاوا و Thread. قسمت پنجم - Executor، ThreadPool، Fork/Join Better together: Java و کلاس Thread. قسمت ششم - آتش دور شوید!
نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION