JavaRush /جاوا بلاگ /Random-UR /آپ دھاگے کے ساتھ جاوا کو برباد نہیں کر سکتے: حصہ چہارم - ...
Viacheslav
سطح

آپ دھاگے کے ساتھ جاوا کو برباد نہیں کر سکتے: حصہ چہارم - قابل کال، مستقبل اور دوست

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

تعارف

ہم پہلے ہی دیکھ چکے ہیں کہ پہلے حصے میں تھریڈز کیسے بنتے ہیں ۔ چلو پھر یاد کرتے ہیں۔ آپ دھاگے سے جاوا کو خراب نہیں کر سکتے: حصہ چہارم - قابل کال، مستقبل اور دوست - 1تھریڈ ایک Threadایسی چیز ہے جو اس میں چلتی ہے 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!";
};
لیکن اس کا کیا کریں؟ ہمیں ایک دھاگے پر چلنے والے کام کی بھی ضرورت کیوں ہے جو نتیجہ لوٹائے؟ ظاہر ہے کہ ہم مستقبل میں انجام پانے والے اعمال کا نتیجہ حاصل کرنے کی توقع رکھتے ہیں۔ انگریزی میں Future - Future. اور بالکل اسی نام کے ساتھ ایک انٹرفیس ہے:java.util.concurrent.Future

java.util.concurrent.Future

java.util.concurrent.Future انٹرفیس ایسے کاموں کے ساتھ کام کرنے کے لیے API کی وضاحت کرتا ہے جن کے نتائج ہم مستقبل میں حاصل کرنے کا ارادہ رکھتے ہیں: نتائج حاصل کرنے کے طریقے، اسٹیٹس چیک کرنے کے طریقے۔ ہم Futureاس کے نفاذ میں دلچسپی رکھتے ہیں java.util.concurrent.FutureTask ۔ یعنی Taskیہ وہی ہے جو اس میں پھانسی دی جائے گی 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مسئلہ سے نتیجہ حاصل کرتے ہیں task۔ (!)اہم، کہ اس وقت طریقہ کار کا استعمال کرتے ہوئے نتیجہ حاصل کیا جاتا ہے، 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--.فراہم کرنے والا. اس کے کوئی پیرامیٹرز نہیں ہیں، لیکن یہ کچھ واپس کرتا ہے، یعنی یہ اسے فراہم کرتا ہے۔ Consumer- صارف یہ ان پٹ (پیرامیٹر s) کے طور پر کچھ لیتا ہے اور اس کے ساتھ کچھ کرتا ہے، یعنی کچھ کھاتا ہے۔ ایک اور فنکشن ہے۔ یہ کچھ لیتا ہے بطور input (پیرامیٹر s)، کچھ کرتا ہے اور کچھ لوٹاتا ہے۔ جیسا کہ ہم دیکھتے ہیں، جنرک فعال طور پر استعمال ہوتے ہیں۔ اگر آپ کو یقین نہیں ہے، تو آپ انہیں یاد کر سکتے ہیں اور " جاوا میں جنرکس کا نظریہ یا قوسین کو عملی طور پر کیسے لگایا جائے " پڑھ سکتے ہیں۔

مکمل مستقبل

جیسے جیسے وقت گزرتا گیا، جاوا 1.8 نے ایک نئی کلاس متعارف کرائی جس کا نام ہے CompletableFuture۔ یہ انٹرفیس کو لاگو کرتا ہے Future، اس کا مطلب ہے کہ ہمارا taskمستقبل میں عمل درآمد کیا جائے گا اور ہم اس پر عمل درآمد کر سکتے ہیں getاور نتیجہ حاصل کر سکتے ہیں۔ لیکن وہ کچھ کو لاگو بھی کرتا ہے CompletionStage۔ ترجمہ سے اس کا مقصد پہلے ہی واضح ہے: یہ کسی قسم کے حساب کا ایک خاص مرحلہ ہے۔ موضوع کا ایک مختصر تعارف جائزہ " تعارف تکمیل کے مرحلے اور مکمل مستقبل " میں پایا جا سکتا ہے۔ آئیے سیدھے بات کی طرف آتے ہیں۔ آئیے شروع کرنے میں ہماری مدد کے لیے دستیاب جامد طریقوں کی فہرست دیکھیں: آپ دھاگے کے ساتھ جاوا کو خراب نہیں کر سکتے: حصہ IV - قابل کال، مستقبل اور دوست - 2ان کو استعمال کرنے کے اختیارات یہ ہیں:
import java.util.concurrent.CompletableFuture;
public class App {
    public static void main(String []args) throws Exception {
        // CompletableFuture уже содержащий результат
        CompletableFuture<String> completed;
        completed = CompletableFuture.completedFuture("Просто meaning");
        // CompletableFuture, запускающий (run) новый поток с Runnable, поэтому он Void
        CompletableFuture<Void> voidCompletableFuture;
        voidCompletableFuture = CompletableFuture.runAsync(() -> {
            System.out.println("run " + Thread.currentThread().getName());
        });
        // CompletableFuture, запускающий новый поток, результат которого возьмём у Supplier
        CompletableFuture<String> supplier;
        supplier = CompletableFuture.supplyAsync(() -> {
            System.out.println("supply " + Thread.currentThread().getName());
            return "Значение";
        });
    }
}
اگر ہم اس کوڈ کو چلاتے ہیں، تو ہم دیکھیں گے کہ تخلیق میں 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 اسٹریم اے پی آئی کی ایک مثال ہے (آپ اس کے بارے میں مزید یہاں پڑھ سکتے ہیں " تصاویر اور مثالوں میں جاوا 8 اسٹریم API گائیڈ ")۔ اگر آپ یہ کوڈ چلاتے ہیں تو یہ Executedظاہر نہیں ہوگا۔ یعنی جاوا میں سٹریم بناتے وقت یہ سلسلہ فوراً شروع نہیں ہوتا بلکہ اس وقت تک انتظار کرتا ہے جب تک کہ اس سے کسی قدر کی ضرورت نہ ہو۔ لیکن CompletableFutureیہ حسابی قیمت کے پوچھے جانے کا انتظار کیے بغیر، فوری طور پر عملدرآمد کے لیے سلسلہ شروع کر دیتا ہے۔ میرے خیال میں یہ سمجھنا ضروری ہے۔ تو ہمارے پاس Completable Future ہے۔ ہم ایک سلسلہ کیسے بنا سکتے ہیں اور ہمارے پاس کیا ذرائع ہیں؟ آئیے ان فنکشنل انٹرفیس کے بارے میں یاد رکھیں جن کے بارے میں ہم نے پہلے لکھا تھا۔
  • ہمارے پاس ایک فنکشن ( Function) ہے جو A لیتا ہے اور B لوٹاتا ہے۔ اس کا ایک طریقہ ہے - apply(درخواست)۔
  • ہمارے پاس ایک صارف ( Consumer) ہے جو A کو قبول کرتا ہے اور کچھ بھی واپس نہیں کرتا ( void )۔ اس کا صرف ایک طریقہ ہے - accept(قبول)۔
  • ہمارے پاس ایک دھاگے پر چلنے والا کوڈ ہے Runnableجو قبول یا واپس نہیں کرتا ہے۔ اس کا ایک ہی طریقہ ہے - run(رن)۔
یاد رکھنے کی دوسری بات یہ ہے کہ CompletalbeFutureیہ اپنے کام میں Runnableصارفین اور افعال کا استعمال کرتا ہے۔ اس کو دیکھتے ہوئے، آپ ہمیشہ یاد رکھ سکتے ہیں کہ آپ 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ہوتے ہیں ۔ _ اس کا مطلب ہے کہ ان مراحل کو ایک نئے دھاگے میں انجام دیا جائے گا۔ اسے ایک خاص تالاب سے لیا جائے گا، اس لیے یہ پہلے سے معلوم نہیں ہے کہ یہ کس قسم کا بہاؤ ہوگا، نیا یا پرانا۔ یہ سب اس بات پر منحصر ہے کہ کام کتنے مشکل ہیں۔ ان طریقوں کے علاوہ، تین اور دلچسپ امکانات ہیں۔ وضاحت کے لیے، آئیے تصور کریں کہ ہمارے پاس ایک مخصوص سروس ہے جسے کہیں سے پیغام موصول ہوتا ہے اور اس میں وقت لگتا ہے: thenApplythenAcceptAsync
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، ہم نتیجہ کا انتظار کرتے ہیں۔ اور ہم نہ صرف جمع کر سکتے ہیں، بلکہ واپس بھی کر سکتے ہیں CompletableFuture:
CompletableFuture.completedFuture(2L)
				.thenCompose((val) -> CompletableFuture.completedFuture(val + 2))
                               .thenAccept(result -> System.out.println(result));
یہاں میں یہ نوٹ کرنا چاہوں گا کہ اختصار کے لیے طریقہ استعمال کیا گیا تھا CompletableFuture.completedFuture۔ یہ طریقہ کوئی نیا دھاگہ نہیں بناتا، لہذا باقی سلسلہ اسی دھاگے میں چلایا جائے گا جس میں اسے کہا گیا تھا completedFuture۔ ایک طریقہ بھی ہے thenAcceptBoth۔ یہ بہت مماثل ہے accept، لیکن اگر thenAcceptیہ قبول کرتا ہے consumer، تو thenAcceptBothیہ ایک اور CompletableStage+ کو بطور input قبول کرتا ہے BiConsumer، یعنی consumer، جو 2 ذرائع کو بطور ان پٹ قبول کرتا ہے، ایک نہیں۔ اس لفظ کے ساتھ ایک اور دلچسپ امکان ہے Either: آپ دھاگے کے ساتھ جاوا کو برباد نہیں کر سکتے: حصہ 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));
یہ کوڈ کچھ نہیں کرے گا، کیونکہ... ایک استثناء پھینک دیا جائے گا اور کچھ نہیں ہوگا. لیکن اگر ہم غیر تبصرہ کرتے ہیں exceptionally، تو ہم طرز عمل کی وضاحت کرتے ہیں۔ میں اس موضوع پر CompletableFutureدرج ذیل ویڈیو دیکھنے کی بھی سفارش کرتا ہوں : میری عاجزانہ رائے میں، یہ ویڈیوز انٹرنیٹ پر سب سے زیادہ بصری ہیں۔ ان سے یہ واضح ہونا چاہیے کہ یہ سب کیسے کام کرتا ہے، ہمارے پاس کون سا ہتھیار ہے اور اس کی ضرورت کیوں ہے۔

نتیجہ

امید ہے کہ اب یہ واضح ہو گیا ہے کہ دھاگوں کا حساب لگانے کے بعد انہیں دوبارہ حاصل کرنے کے لیے کیسے استعمال کیا جا سکتا ہے۔ اضافی مواد: #ویاچسلاو
تبصرے
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION