CodeGym /جاوا بلاگ /Random-UR /ایک ساتھ بہتر: جاوا اور تھریڈ کلاس۔ حصہ چہارم — کال قابل،...
John Squirrels
سطح
San Francisco

ایک ساتھ بہتر: جاوا اور تھریڈ کلاس۔ حصہ چہارم — کال قابل، مستقبل، اور دوست

گروپ میں شائع ہوا۔

تعارف

حصہ اول میں ، ہم نے جائزہ لیا کہ تھریڈز کیسے بنتے ہیں۔ آئیے ایک بار پھر یاد کرتے ہیں۔ ایک ساتھ بہتر: جاوا اور تھریڈ کلاس۔  حصہ چہارم — کال قابل، مستقبل، اور دوست - 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 کہتے ہیں جو 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 کا تعارف۔ آئیے بات کی طرف آتے ہیں۔ آئیے دستیاب جامد طریقوں کی فہرست کو دیکھتے ہیں جو ہمیں شروع کرنے میں مدد کریں گے: ایک ساتھ بہتر: جاوا اور تھریڈ کلاس۔  حصہ چہارم — کال قابل، مستقبل، اور دوست - 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پائپ لائن کو فوری طور پر کام کرنا شروع کر دیتا ہے، بغیر کسی کے اس کی قیمت پوچھنے کا انتظار کیے بغیر۔ میرے خیال میں یہ سمجھنا ضروری ہے۔ ایس او، ہمارے پاس ایک ہے 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);
}
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بلکہ واپس بھی کر سکتے ہیں CompletableFuturea

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" شامل ہے: ایک ساتھ بہتر: جاوا اور تھریڈ کلاس۔  حصہ چہارم — کال قابل، مستقبل، اور دوست - 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، میں آپ کو مندرجہ ذیل ویڈیو دیکھنے کا مشورہ بھی دیتا ہوں: میری عاجزانہ رائے میں، یہ انٹرنیٹ پر سب سے زیادہ وضاحتی ویڈیوز میں سے ہیں۔ انہیں یہ واضح کرنا چاہیے کہ یہ سب کیسے کام کرتا ہے، ہمارے پاس کون سی ٹول کٹ دستیاب ہے، اور اس سب کی ضرورت کیوں ہے۔

نتیجہ

امید ہے کہ، اب یہ واضح ہو گیا ہے کہ آپ دھاگوں کے مکمل ہونے کے بعد حساب حاصل کرنے کے لیے کس طرح استعمال کر سکتے ہیں۔ اضافی مواد: ایک ساتھ بہتر: جاوا اور تھریڈ کلاس۔ حصہ اول — عمل درآمد کے دھاگے ایک ساتھ بہتر: جاوا اور تھریڈ کلاس۔ حصہ دوم — ہم آہنگی ایک ساتھ بہتر: جاوا اور تھریڈ کلاس۔ حصہ III - ایک دوسرے کے ساتھ بہتر تعامل: جاوا اور تھریڈ کلاس۔ حصہ V — ایگزیکیوٹر، تھریڈ پول، فورک/ جوائن بیٹر ایک ساتھ: جاوا اور تھریڈ کلاس۔ حصہ VI - آگ دور!
تبصرے
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION