تعارف
حصہ اول
میں ، ہم نے جائزہ لیا کہ تھریڈز کیسے بنتے ہیں۔ آئیے ایک بار پھر یاد کرتے ہیں۔
ایک تھریڈ کی نمائندگی تھریڈ کلاس کے ذریعہ کی جاتی ہے، جس کا
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
کہتے ہیں جو Java 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()
، تو عملدرآمد ہم آہنگ ہو جاتا ہے! آپ کے خیال میں یہاں کون سا طریقہ کار استعمال کیا جائے گا؟ سچ ہے، کوئی مطابقت پذیری بلاک نہیں ہے۔ اسی لیے ہم JVisualVM میں
WAITING کو
monitor
a یا کے طور پر نہیں دیکھیں گے
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
انٹرفیس کو بھی لاگو کرتا ہے۔ نام یہ سب کہتا ہے: یہ حسابات کے کچھ سیٹ کا ایک خاص مرحلہ ہے۔ موضوع کا ایک مختصر تعارف یہاں کے جائزے میں پایا جا سکتا ہے: CompletionStage اور Completable Future کا تعارف۔ آئیے بات کی طرف آتے ہیں۔ آئیے دستیاب جامد طریقوں کی فہرست کو دیکھتے ہیں جو ہمیں شروع کرنے میں مدد کریں گے:
ان کو استعمال کرنے کے اختیارات یہ ہیں:
import java.util.concurrent.CompletableFuture;
public class App {
public static void main(String[] args) throws Exception {
CompletableFuture<String> completed;
completed = CompletableFuture.completedFuture("Just a value");
CompletableFuture<Void> voidCompletableFuture;
voidCompletableFuture = CompletableFuture.runAsync(() -> {
System.out.println("run " + Thread.currentThread().getName());
});
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
پائپ لائن کو فوری طور پر کام کرنا شروع کر دیتا ہے، بغیر کسی کے اس کی قیمت پوچھنے کا انتظار کیے بغیر۔ میرے خیال میں یہ سمجھنا ضروری ہے۔ ایس او، ہمارے پاس ایک ہے
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.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
فراہم کرنے والی دیگر صلاحیتوں پر ایک نظر ڈالتے ہیں۔ ہم ایک کے نتیجے کو
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
a
CompletableFuture.completedFuture(2L)
.thenCompose((val) -> CompletableFuture.completedFuture(val + 2))
.thenAccept(result -> System.out.println(result));
یہاں میں یہ نوٹ کرنا چاہتا ہوں کہ
CompletableFuture.completedFuture()
طریقہ اختصار کے لیے استعمال کیا گیا تھا۔ یہ طریقہ نیا دھاگہ نہیں بناتا، اس لیے باقی پائپ لائن اسی دھاگے پر چلائی جائے گی جہاں
completedFuture
بلایا گیا تھا۔ ایک طریقہ بھی ہے
thenAcceptBoth()
۔ یہ بہت مماثل ہے
accept()
، لیکن اگر
thenAccept()
قبول کرتا ہے تو
Consumer
،
thenAcceptBoth()
دوسرے
CompletableStage
+ کو
BiConsumer
بطور ان پٹ قبول کرتا ہے، یعنی a
consumer
جو ایک کی بجائے 2 ذرائع لیتا ہے۔ طریقوں کے ذریعہ پیش کردہ ایک اور دلچسپ قابلیت ہے جس کے نام میں لفظ "Either" شامل ہے:
یہ طریقے متبادل کو قبول کرتے ہیں
CompletableStage
اور اس پر عمل درآمد کیا جاتا ہے
CompletableStage
جس پر پہلے عمل کیا جائے۔ آخر میں، میں اس جائزے کو ایک اور دلچسپ خصوصیت کے ساتھ ختم کرنا چاہتا ہوں
CompletableFuture
: ایرر ہینڈلنگ۔
CompletableFuture.completedFuture(2L)
.thenApply((a) -> {
throw new IllegalStateException("error");
}).thenApply((a) -> 3L)
.thenAccept(val -> System.out.println(val));
یہ کوڈ کچھ نہیں کرے گا، کیونکہ ایک استثناء ہوگا اور کچھ نہیں ہوگا۔ لیکن "غیر معمولی" بیان پر تبصرہ کرتے ہوئے، ہم متوقع رویے کی وضاحت کرتے ہیں۔ کی بات کرتے ہوئے
CompletableFuture
، میں آپ کو مندرجہ ذیل ویڈیو دیکھنے کا مشورہ بھی دیتا ہوں:
میری عاجزانہ رائے میں، یہ انٹرنیٹ پر سب سے زیادہ وضاحتی ویڈیوز میں سے ہیں۔ انہیں یہ واضح کرنا چاہیے کہ یہ سب کیسے کام کرتا ہے، ہمارے پاس کون سی ٹول کٹ دستیاب ہے، اور اس سب کی ضرورت کیوں ہے۔
نتیجہ
امید ہے کہ، اب یہ واضح ہو گیا ہے کہ آپ دھاگوں کے مکمل ہونے کے بعد حساب حاصل کرنے کے لیے کس طرح استعمال کر سکتے ہیں۔ اضافی مواد:
ایک ساتھ بہتر: جاوا اور تھریڈ کلاس۔ حصہ اول — عمل درآمد کے دھاگے
ایک ساتھ بہتر: جاوا اور تھریڈ کلاس۔ حصہ دوم — ہم آہنگی
ایک ساتھ بہتر: جاوا اور تھریڈ کلاس۔ حصہ III -
ایک دوسرے کے ساتھ بہتر تعامل: جاوا اور تھریڈ کلاس۔ حصہ V — ایگزیکیوٹر، تھریڈ پول، فورک/
جوائن بیٹر ایک ساتھ: جاوا اور تھریڈ کلاس۔ حصہ VI - آگ دور!
GO TO FULL VERSION