ভূমিকা
প্রথম অংশে , আমরা পর্যালোচনা করেছি কিভাবে থ্রেড তৈরি করা হয়। আরেকবার স্মরণ করা যাক।
![একসাথে ভাল: জাভা এবং থ্রেড ক্লাস। পার্ট IV — আহ্বানযোগ্য, ভবিষ্যত, এবং বন্ধু - 1]()
একটি থ্রেড থ্রেড ক্লাস দ্বারা প্রতিনিধিত্ব করা হয়, যার
run()
পদ্ধতি বলা হয়। তাহলে আসুন
টিউটোরিয়াল পয়েন্ট অনলাইন জাভা কম্পাইলার ব্যবহার করি এবং নিম্নলিখিত কোডটি কার্যকর করি:
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()
পদ্ধতি ঘোষণা করে যা ফলাফল প্রদান করে। এছাড়াও, এটি ডিফল্টরূপে ব্যতিক্রম নিক্ষেপ করে। অর্থাৎ, এটি
try-catch
চেক করা ব্যতিক্রমগুলির জন্য ব্লক করা থেকে আমাদের বাঁচায়। খারাপ না, তাই না? এর পরিবর্তে এখন আমাদের একটি নতুন কাজ আছে
Runnable
:
Callable task = () -> {
return "Hello, World!";
};
কিন্তু আমরা এটা দিয়ে কি করব? কেন আমাদের একটি থ্রেডে চলমান একটি টাস্ক দরকার যা ফলাফল দেয়? স্পষ্টতই, ভবিষ্যতে সম্পাদিত যেকোনো কর্মের জন্য, আমরা ভবিষ্যতে সেই কর্মের ফলাফল পাওয়ার আশা করি। এবং আমাদের একটি সংশ্লিষ্ট নামের সাথে একটি ইন্টারফেস আছে:
java.util.concurrent.Future
java.util.concurrent.Future
java.util.concurrent.Future ইন্টারফেস এমন কাজের সাথে কাজ করার জন্য একটি API সংজ্ঞায়িত করে যার ফলাফল আমরা ভবিষ্যতে পাওয়ার পরিকল্পনা করছি: ফলাফল পাওয়ার পদ্ধতি এবং স্ট্যাটাস চেক করার পদ্ধতি
। সম্পর্কে , আমরা
java.util.concurrent.FutureTaskFuture
ক্লাসে এর বাস্তবায়নে আগ্রহী । এটি হল "টাস্ক" যা সম্পাদন করা হবে ৷ যা এই বাস্তবায়নকে আরও আকর্ষণীয় করে তোলে তা হল এটি Runnable প্রয়োগ করে। আপনি এটিকে থ্রেডের কাজগুলির সাথে কাজ করার পুরানো মডেল এবং নতুন মডেলের মধ্যে এক ধরণের অ্যাডাপ্টার বিবেচনা করতে পারেন (এই অর্থে যে এটি জাভা 1.5-এ উপস্থিত হয়েছিল নতুন)। এখানে একটি উদাহরণ:
Future
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()
, তখন মৃত্যুদন্ড সিঙ্ক্রোনাস হয়ে যায়! আপনি এখানে কি ব্যবস্থা ব্যবহার করা হবে বলে মনে করেন? সত্য, কোন সিঙ্ক্রোনাইজেশন ব্লক নেই। সেজন্য আমরা JVisualVM-এ
WAITING কে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
সময় অতিবাহিত হয় এবং জাভা 1.8-এ একটি নতুন ক্লাস উপস্থিত হয়। এটি
Future
ইন্টারফেস প্রয়োগ করে, অর্থাৎ আমাদের কাজগুলি ভবিষ্যতে সম্পন্ন হবে, এবং আমরা
get()
ফলাফল পেতে কল করতে পারি। কিন্তু এটি
CompletionStage
ইন্টারফেস প্রয়োগ করে। নামটি সব বলে: এটি গণনার কিছু সেটের একটি নির্দিষ্ট পর্যায়। বিষয়ের একটি সংক্ষিপ্ত ভূমিকা এখানে পর্যালোচনায় পাওয়া যাবে: Introduction to CompletionStage এবং Completable Future. এর সঠিক পয়েন্ট পেতে যাক. আসুন উপলব্ধ স্ট্যাটিক পদ্ধতিগুলির তালিকা দেখি যা আমাদের শুরু করতে সাহায্য করবে:
![একসাথে ভাল: জাভা এবং থ্রেড ক্লাস। পার্ট IV — আহ্বানযোগ্য, ভবিষ্যত, এবং বন্ধুরা - 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
সাথে একটি সম্পূর্ণ পাইপলাইন চালু করা জড়িত। অতএব, Java8 থেকে SteamAPI-এর সাথে একটি নির্দিষ্ট মিলের সাথে, এখানেই আমরা এই পদ্ধতির মধ্যে পার্থক্য খুঁজে পাই। উদাহরণ স্বরূপ:
List<String> array = Arrays.asList("one", "two");
Stream<String> stringStream = array.stream().map(value -> {
System.out.println("Executed");
return value.toUpperCase();
});
এটি জাভা 8 এর স্ট্রিম API এর একটি উদাহরণ। আপনি যদি এই কোডটি চালান, তাহলে আপনি দেখতে পাবেন যে "Executed" প্রদর্শিত হবে না। অন্য কথায়, যখন জাভাতে একটি স্ট্রিম তৈরি করা হয়, তখন স্ট্রিমটি অবিলম্বে শুরু হয় না। পরিবর্তে, এটি অপেক্ষা করে যে কেউ এটি থেকে একটি মান চায়। কিন্তু
CompletableFuture
পাইপলাইনটি অবিলম্বে কার্যকর করা শুরু করে, কারও কাছে এটির মূল্য জিজ্ঞাসা করার অপেক্ষা না করে। আমি মনে করি এটি বোঝা গুরুত্বপূর্ণ। S o, আমাদের আছে একটি
CompletableFuture
. আমরা কিভাবে একটি পাইপলাইন (বা চেইন) তৈরি করতে পারি এবং আমাদের কি প্রক্রিয়া আছে? সেই কার্যকরী ইন্টারফেসগুলি স্মরণ করুন যা আমরা আগে লিখেছিলাম।
- আমাদের একটি আছে
Function
যা একটি A নেয় এবং একটি B প্রদান করে। এটির একটি একক পদ্ধতি রয়েছে: apply()
.
- আমাদের একটি আছে
Consumer
যা একটি A নেয় এবং কিছুই ফেরত দেয় না (অকার্যকর)। এটি একটি একক পদ্ধতি আছে: accept()
.
- আমাদের আছে
Runnable
, যা থ্রেডের উপর চলে, এবং কিছুই নেয় না এবং কিছুই ফেরত দেয় না। এটি একটি একক পদ্ধতি আছে: run()
.
মনে রাখার পরের জিনিসটি হল , , এবং এর কাজে
CompletableFuture
ব্যবহার করে । তদনুসারে, আপনি সর্বদা জানতে পারেন যে আপনি নিম্নলিখিতগুলি করতে পারেন :
Runnable
Consumers
Functions
CompletableFuture
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);
}
, , এবং পদ্ধতিগুলির "Async"
thenRun()
সংস্করণ রয়েছে। এর মানে হল যে এই ধাপগুলি একটি ভিন্ন থ্রেডে সম্পন্ন হবে। এই থ্রেডটি একটি বিশেষ পুল থেকে নেওয়া হবে — তাই এটি একটি নতুন বা পুরানো থ্রেড কিনা তা আমরা আগে থেকে জানতে পারব না। এটা সব নির্ভর করে কাজগুলো কতটা গণনামূলক-নিবিড় তার উপর। এই পদ্ধতিগুলি ছাড়াও, আরও তিনটি আকর্ষণীয় সম্ভাবনা রয়েছে। স্বচ্ছতার জন্য, আসুন কল্পনা করি যে আমাদের একটি নির্দিষ্ট পরিষেবা রয়েছে যা কোথাও থেকে কিছু ধরণের বার্তা পায় — এবং এটি সময় নেয়:
thenApply()
thenAccept()
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();
মনে রাখবেন যে থ্রেডগুলি ডিফল্টরূপে ডেমন থ্রেড, তাই স্পষ্টতার জন্য, আমরা
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()
অন্য
CompletableStage
+কে
BiConsumer
ইনপুট হিসেবে গ্রহণ করে, অর্থাৎ a
consumer
যেটি একটির পরিবর্তে 2টি উৎস নেয়। পদ্ধতিগুলির দ্বারা প্রস্তাবিত আরেকটি আকর্ষণীয় ক্ষমতা রয়েছে যার নাম "হয়" শব্দটি অন্তর্ভুক্ত করে:
![একসাথে ভাল: জাভা এবং থ্রেড ক্লাস। পার্ট IV — আহ্বানযোগ্য, ভবিষ্যত, এবং বন্ধু - 3]()
এই পদ্ধতিগুলি একটি বিকল্প গ্রহণ করে
CompletableStage
এবং
CompletableStage
প্রথমে যেটি কার্যকর করা হবে তার উপর কার্যকর করা হয়। অবশেষে, আমি এই পর্যালোচনাটির আরেকটি আকর্ষণীয় বৈশিষ্ট্য দিয়ে শেষ করতে চাই
CompletableFuture
: ত্রুটি পরিচালনা।
CompletableFuture.completedFuture(2L)
.thenApply((a) -> {
throw new IllegalStateException("error");
}).thenApply((a) -> 3L)
//.exceptionally(ex -> 0L)
.thenAccept(val -> System.out.println(val));
এই কোড কিছুই করবে না, কারণ একটি ব্যতিক্রম হবে এবং অন্য কিছুই ঘটবে না। কিন্তু "অসাধারণভাবে" বিবৃতিতে মন্তব্য না করে, আমরা প্রত্যাশিত আচরণকে সংজ্ঞায়িত করি। এর কথা বলতে গিয়ে
CompletableFuture
, আমি আপনাকে নিম্নলিখিত ভিডিওটি দেখার পরামর্শ দিচ্ছি:
আমার বিনীত মতামত, এই ইন্টারনেটে সবচেয়ে ব্যাখ্যামূলক ভিডিওগুলির মধ্যে একটি. তাদের এটি পরিষ্কার করা উচিত যে এটি কীভাবে কাজ করে, আমাদের কাছে কোন টুলকিট উপলব্ধ রয়েছে এবং কেন এই সমস্ত প্রয়োজন।
উপসংহার
আশা করি, এটি এখন পরিষ্কার হয়ে গেছে যে আপনি কীভাবে থ্রেডগুলি সম্পূর্ণ করার পরে গণনা পেতে ব্যবহার করতে পারেন। অতিরিক্ত উপাদান:
একসাথে ভাল: জাভা এবং থ্রেড ক্লাস। পার্ট I — এক্সিকিউশনের থ্রেডগুলি একসাথে ভাল: জাভা এবং থ্রেড ক্লাস। পার্ট II — সিঙ্ক্রোনাইজেশন আরও ভাল একসাথে: জাভা এবং থ্রেড ক্লাস। পার্ট III — মিথস্ক্রিয়া আরও ভাল একসাথে: জাভা এবং থ্রেড ক্লাস। পার্ট V — এক্সিকিউটর, থ্রেডপুল, ফর্ক/জইন বেটার একসাথে: জাভা এবং থ্রেড ক্লাস। পার্ট VI — আগুন দূরে!
GO TO FULL VERSION