CodeGym /Java Blog /यादृच्छिक /एकत्र चांगले: Java आणि थ्रेड वर्ग. भाग IV - कॉल करण्यायोग...
John Squirrels
पातळी 41
San Francisco

एकत्र चांगले: Java आणि थ्रेड वर्ग. भाग IV - कॉल करण्यायोग्य, भविष्य आणि मित्र

यादृच्छिक या ग्रुपमध्ये प्रकाशित केले

परिचय

भाग I मध्ये , आम्ही थ्रेड कसे तयार केले जातात याचे पुनरावलोकन केले. अजून एकदा आठवूया. एकत्र चांगले: Java आणि थ्रेड वर्ग.  भाग 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 नावाचा भाऊ आहे जो Java 1.5 मध्ये जगात आला. फरक काय आहेत? जर तुम्ही या इंटरफेससाठी Javadoc कडे बारकाईने पाहिले तर, आम्हाला दिसेल की, विपरीत Runnable, नवीन इंटरफेस एक call()पद्धत घोषित करतो जी परिणाम देते. तसेच, ते डीफॉल्टनुसार अपवाद टाकते. try-catchम्हणजेच, हे आम्हाला तपासलेल्या अपवादांसाठी ब्लॉक करण्यापासून वाचवते . वाईट नाही, बरोबर? आता आमच्याकडे त्याऐवजी नवीन कार्य आहे Runnable:

Callable task = () -> {
	return "Hello, World!";
};
पण आपण त्याचे काय करायचे? आम्हाला परिणाम देणार्‍या धाग्यावर चालणारे कार्य का हवे आहे? साहजिकच, भविष्यात केलेल्या कोणत्याही कृतीसाठी, आम्ही भविष्यात त्या क्रियांचे परिणाम प्राप्त करण्याची अपेक्षा करतो. आणि आमच्याकडे संबंधित नावाचा इंटरफेस आहे:java.util.concurrent.Future

java.util.concurrent.भविष्य

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 मध्ये WAITINGmonitor एक किंवा म्हणून पाहणार नाही wait, परंतु परिचित park()पद्धत म्हणून (कारण LockSupportयंत्रणा वापरली जात आहे).

कार्यात्मक इंटरफेस

पुढे, आम्ही Java 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. हे इनपुट (वितर्क) घेते, काहीतरी करते आणि काहीतरी परत करते. आपण पाहू शकता की आम्ही सक्रियपणे जेनेरिक वापरत आहोत. जर तुम्हाला खात्री नसेल, तर तुम्ही " Java मधील जेनेरिक्स: सराव मध्ये angled brackets कसे वापरावे " हे वाचून रिफ्रेशर मिळवू शकता .

पूर्ण करण्यायोग्य भविष्य

CompletableFutureवेळ निघून गेला आणि Java 1.8 मध्ये एक नवीन वर्ग दिसू लागला. हे Futureइंटरफेस लागू करते, म्हणजे आमची कार्ये भविष्यात पूर्ण होतील, आणि आम्ही get()परिणाम मिळविण्यासाठी कॉल करू शकतो. परंतु ते CompletionStageइंटरफेस देखील लागू करते. नाव हे सर्व सांगते: गणनांच्या काही संचाचा हा एक विशिष्ट टप्पा आहे. या विषयाचा संक्षिप्त परिचय येथे पुनरावलोकनामध्ये आढळू शकतो: पूर्णत्वाची अवस्था आणि पूर्ण करण्यायोग्य भविष्याचा परिचय. चला थेट मुद्द्यापर्यंत पोहोचूया. चला उपलब्ध स्टॅटिक पद्धतींची सूची पाहूया जी आम्हाला प्रारंभ करण्यास मदत करतील: एकत्र चांगले: Java आणि थ्रेड वर्ग.  भाग 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();
});
हे Java 8 च्या Stream API चे उदाहरण आहे. तुम्ही हा कोड चालवल्यास, तुम्हाला दिसेल की "एक्झिक्युटेड" प्रदर्शित होणार नाही. दुसऱ्या शब्दांत, जेव्हा Java मध्ये प्रवाह तयार होतो, तेव्हा प्रवाह लगेच सुरू होत नाही. त्याऐवजी, एखाद्याला त्यातून मूल्य हवे असेल याची प्रतीक्षा करते. परंतु CompletableFutureकोणीतरी त्याचे मूल्य विचारेल याची वाट न पाहता लगेच पाइपलाइन कार्यान्वित करण्यास सुरवात करते. हे समजून घेणे महत्त्वाचे आहे असे मला वाटते. एस ओ, आमच्याकडे एक आहे CompletableFuture. आम्ही पाइपलाइन (किंवा साखळी) कशी बनवू शकतो आणि आमच्याकडे कोणती यंत्रणा आहे? त्या फंक्शनल इंटरफेसची आठवण करा ज्याबद्दल आम्ही आधी लिहिले होते.
  • आमच्याकडे A आहे Functionजो A घेतो आणि B परत करतो. त्याची एकच पद्धत आहे: apply().
  • आमच्याकडे ए आहे 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);
}
, , आणि पद्धतींमध्ये "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()स्वीकारते , म्हणजे a जे एका ऐवजी 2 स्त्रोत घेते. पद्धतींद्वारे ऑफर केलेली आणखी एक मनोरंजक क्षमता आहे ज्यांच्या नावात "एकतर" शब्दाचा समावेश आहे: या पद्धती पर्यायी स्वीकारतात आणि प्रथम अंमलात आणल्या जातात त्यावर अंमलात आणल्या जातात . शेवटी, मला हे पुनरावलोकन आणखी एका मनोरंजक वैशिष्ट्यासह समाप्त करायचे आहे : त्रुटी हाताळणे. CompletableStageBiConsumerconsumerएकत्र चांगले: Java आणि थ्रेड वर्ग.  भाग IV - कॉल करण्यायोग्य, भविष्य आणि मित्र - 3CompletableStageCompletableStageCompletableFuture

CompletableFuture.completedFuture(2L)
				 .thenApply((a) -> {
					throw new IllegalStateException("error");
				 }).thenApply((a) -> 3L)
				 //.exceptionally(ex -> 0L)
				 .thenAccept(val -> System.out.println(val));
हा कोड काहीही करणार नाही, कारण अपवाद असेल आणि दुसरे काहीही होणार नाही. परंतु "अपवादात्मक" विधानावर टिप्पणी करून, आम्ही अपेक्षित वर्तन परिभाषित करतो. बद्दल बोलताना CompletableFuture, मी तुम्हाला खालील व्हिडिओ पाहण्याची देखील शिफारस करतो: माझ्या नम्र मते, हे इंटरनेटवरील सर्वात स्पष्टीकरणात्मक व्हिडिओंपैकी एक आहेत. हे सर्व कसे कार्य करते, आमच्याकडे कोणती टूलकिट उपलब्ध आहे आणि हे सर्व का आवश्यक आहे हे त्यांनी स्पष्ट केले पाहिजे.

निष्कर्ष

आशेने, थ्रेड पूर्ण झाल्यानंतर गणना मिळविण्यासाठी तुम्ही कसे वापरू शकता हे आता स्पष्ट झाले आहे. अतिरिक्त साहित्य: एकत्र चांगले: Java आणि थ्रेड वर्ग. भाग I — अंमलबजावणीचे धागे एकत्र चांगले: Java आणि थ्रेड क्लास. भाग II — एकत्रितपणे सिंक्रोनाइझेशन उत्तम: Java आणि थ्रेड वर्ग. भाग तिसरा — परस्परसंवाद चांगले एकत्र: Java आणि थ्रेड वर्ग. भाग V — एक्झिक्युटर, थ्रेडपूल, फोर्क/जॉईन बेटर एकत्र: Java आणि थ्रेड क्लास. भाग VI - आग दूर!
टिप्पण्या
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION