CodeGym /Java Blogu /Rastgele /Birlikte daha iyi: Java ve Thread sınıfı. Bölüm IV - Call...
John Squirrels
Seviye
San Francisco

Birlikte daha iyi: Java ve Thread sınıfı. Bölüm IV - Callable, Future ve arkadaşlar

grupta yayınlandı

giriiş

Bölüm I'de , ileti dizilerinin nasıl oluşturulduğunu inceledik. Bir kez daha hatırlayalım. Bir iş parçacığı, yöntemi çağrılan Birlikte daha iyi: Java ve Thread sınıfı.  Bölüm IV — Callable, Future ve arkadaşlar - 1Thread sınıfı tarafından temsil edilir . Öyleyse Tutorialspoint çevrimiçi Java derleyicisinirun() kullanalım ve aşağıdaki kodu çalıştıralım:

public class HelloWorld {
    
    public static void main(String[] args) {
        Runnable task = () -> {
            System.out.println("Hello World");
        };
        new Thread(task).start();
    }
}
Bir iş parçacığında görev başlatmak için tek seçenek bu mu?

java.util.concurrent.Callable

Java.lang.Runnable'ın java.util.concurrent.Callable adında Java 1.5 ile dünyaya gelen bir erkek kardeşi olduğu ortaya çıktı . Farklılıklar nedir? Bu arabirim için Javadoc'a yakından bakarsanız, Runnableyeni arabirimin aksine, call()sonuç döndüren bir yöntem bildirdiğini görürüz. Ayrıca, varsayılan olarak İstisna atar. try-catchYani, bizi kontrol edilen istisnalar için engelleme zorunluluğundan kurtarır . Fena değil, değil mi? Şimdi bunun yerine yeni bir görevimiz var Runnable:

Callable task = () -> {
	return "Hello, World!";
};
Ama onunla ne yapacağız? Sonuç döndüren bir iş parçacığında çalışan bir göreve neden ihtiyacımız var? Açıkçası, gelecekte gerçekleştirilen herhangi bir eylem için, gelecekte bu eylemlerin sonucunu almayı bekliyoruz. Ve karşılık gelen ada sahip bir arayüzümüz var:java.util.concurrent.Future

java.util.concurrent.Future

Java.util.concurrent.Future arabirimi, sonuçlarını gelecekte almayı planladığımız görevlerle çalışmak için bir API tanımlar: sonuç alma yöntemleri ve durumu kontrol etme yöntemleri . ile ilgili olarak , java.util.concurrent.FutureTaskFuture sınıfındaki uygulamasıyla ilgileniyoruz . Bu , . Bu uygulamayı daha da ilginç kılan, Runnable'ı da uygulamasıdır. Bunu, iş parçacığı üzerindeki görevlerle çalışan eski model ile yeni model (Java 1.5'te ortaya çıkması anlamında yeni) arasında bir tür bağdaştırıcı olarak düşünebilirsiniz. İşte bir örnek: 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());
    }
}
Örnekten de görebileceğiniz gibi, getgörevden sonuç almak için yöntemi kullanıyoruz. Not:yöntemi kullanarak sonucu aldığınızda get(), yürütme senkronize olur! Sizce burada nasıl bir mekanizma kullanılacak? Doğru, senkronizasyon bloğu yok. Bu nedenle WAITING'i JVisualVM'de veya olarak monitordeğil wait, bilinen yöntem olarak göreceğiz park()(çünkü LockSupportmekanizma kullanılıyor).

fonksiyonel arayüzler

Şimdi, Java 1.8'deki sınıflar hakkında konuşacağız, bu nedenle kısa bir giriş yapsak iyi olur. Aşağıdaki koda bakın:

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);
	}
};
Bir sürü ekstra kod, öyle değil mi? Bildirilen sınıfların her biri bir işlevi yerine getirir, ancak onu tanımlamak için bir sürü ekstra destekleyici kod kullanırız. Ve Java geliştiricileri böyle düşündü. Buna göre, bir dizi "işlevsel arabirim" ( @FunctionalInterface) tanıttılar ve artık Java'nın "düşünmeyi" kendisinin yapacağına karar verdiler ve bize yalnızca önemli şeyleri endişelendirmek için bıraktılar:

Supplier<String> supplier = () -> "String";
Consumer<String> consumer = s -> System.out.println(s);
Function<String, Integer> converter = s -> Integer.valueOf(s);
Bir Suppliersarf malzemeleri. Hiçbir parametresi yoktur, ancak bir şey döndürür. Eşyaları bu şekilde tedarik ediyor. Bir Consumertüketir. Bir şeyi girdi (argüman) olarak alır ve onunla bir şeyler yapar. Argüman ne tükettiğidir. O zaman bizde de var Function. Girdiler (argümanlar) alır, bir şeyler yapar ve bir şeyler döndürür. Aktif olarak jenerik kullandığımızı görebilirsiniz. Emin değilseniz, " Java'da Jenerikler: Uygulamada köşeli ayraçların nasıl kullanılacağı " bölümünü okuyarak bilgi tazeleyebilirsiniz .

TamamlanabilirGelecek

CompletableFutureZaman geçti ve Java 1.8'de adında yeni bir sınıf ortaya çıktı. Arayüzü uygular Future, yani görevlerimiz ileride tamamlanacak ve get()sonucu almak için arayabiliriz. Ama aynı zamanda arayüzü de uygular CompletionStage. Adı her şeyi söylüyor: Bu, bir dizi hesaplamanın belirli bir aşamasıdır. Konuya kısa bir giriş, buradaki incelemede bulunabilir: CompletionStage ve CompletableFuture'a Giriş. Hemen konuya gelelim. Başlamamıza yardımcı olacak mevcut statik yöntemlerin listesine bir göz atalım: Birlikte daha iyi: Java ve Thread sınıfı.  Bölüm IV — Callable, Future ve arkadaşlar - 2Bunları kullanma seçenekleri şunlardır:

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";
        });
    }
}
Bu kodu çalıştırırsak, a oluşturmanın tüm bir işlem hattını başlatmayı da içerdiğini göreceğiz CompletableFuture. Bu nedenle, Java8'deki SteamAPI ile belirli bir benzerlikle, bu yaklaşımlar arasındaki farkı burada buluyoruz. Örneğin:

List<String> array = Arrays.asList("one", "two");
Stream<String> stringStream = array.stream().map(value -> {
	System.out.println("Executed");
	return value.toUpperCase();
});
Bu, Java 8'in Akış API'sine bir örnektir. Bu kodu çalıştırırsanız, "Yürütüldü" ifadesinin görüntülenmeyeceğini göreceksiniz. Başka bir deyişle, Java'da bir akış oluşturulduğunda, akış hemen başlamaz. Bunun yerine, birinin ondan bir değer istemesini bekler. Ancak CompletableFuture, birisinin ondan bir değer istemesini beklemeden işlem hattını hemen yürütmeye başlar. Bunun anlaşılmasının önemli olduğunu düşünüyorum. Yani, elimizde bir CompletableFuture. Nasıl bir boru hattı (veya zincir) yapabiliriz ve hangi mekanizmalara sahibiz? Daha önce yazdığımız işlevsel arayüzleri hatırlayın.
  • A alan ve B döndüren bir a'mız var Function. Tek bir yöntemi var: apply().
  • ConsumerA alan ve hiçbir şey döndürmeyen bir a'mız var (Void). Tek bir yöntemi vardır: accept().
  • Runnableİş parçacığı üzerinde çalışan ve hiçbir şey almayan ve hiçbir şey döndürmeyen var . Tek bir yöntemi vardır: run().
Hatırlanması gereken bir sonraki şey, işinde ve , öğesinin CompletableFuturekullanılmasıdır . Buna göre, aşağıdakileri yapabileceğinizi her zaman bilebilirsiniz : 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);
}
thenRun(), thenApply()ve yöntemlerinin thenAccept()"Async" sürümleri vardır. Bu, bu aşamaların farklı bir iş parçacığı üzerinde tamamlanacağı anlamına gelir. Bu ileti dizisi özel bir havuzdan alınacak — yani bunun yeni mi yoksa eski bir ileti dizisi mi olacağını önceden bilemeyeceğiz. Her şey, görevlerin hesaplama açısından ne kadar yoğun olduğuna bağlıdır. Bu yöntemlere ek olarak, üç ilginç olasılık daha var. Netleştirmek için, bir yerden bir tür mesaj alan belirli bir hizmetimiz olduğunu düşünelim - ve bu zaman alıyor:

public static class NewsService {
	public static String getMessage() {
		try {
			Thread.currentThread().sleep(3000);
			return "Message";
		} catch (InterruptedException e) {
			throw new IllegalStateException(e);
		}
	}
}
Şimdi, sağlayan diğer yeteneklere bir göz atalım CompletableFuture. CompletableFuturea'nın sonucunu diğerinin sonucuyla birleştirebiliriz 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()İş parçacıklarının varsayılan olarak arka plan programı iş parçacıkları olduğuna dikkat edin, bu nedenle netlik için sonucu beklemek için kullanırız . Sadece birleştirmekle kalmaz CompletableFutures, aynı zamanda bir de döndürebiliriz CompletableFuture:

CompletableFuture.completedFuture(2L)
				.thenCompose((val) -> CompletableFuture.completedFuture(val + 2))
                               .thenAccept(result -> System.out.println(result));
CompletableFuture.completedFuture()Burada yöntemin kısalık için kullanıldığını belirtmek isterim . completedFutureBu yöntem yeni bir iş parçacığı oluşturmaz, bu nedenle işlem hattının geri kalanı çağrıldığı aynı iş parçacığında yürütülür . Ayrıca bir yöntem var thenAcceptBoth(). 'a çok benzer accept(), ancak thenAccept()a'yı kabul ederse Consumer, başka bir +'yı girdi olarak thenAcceptBoth()kabul eder , yani bir yerine 2 kaynak alan bir a. Adında "Ya" sözcüğü bulunan yöntemlerin sunduğu ilginç bir yetenek daha vardır: Bu yöntemler bir alternatifi kabul eder ve hangisi önce yürütülürse o üzerinde yürütülür . Son olarak, bu incelemeyi başka bir ilginç özellik ile bitirmek istiyorum : hata işleme. CompletableStageBiConsumerconsumerBirlikte daha iyi: Java ve Thread sınıfı.  Bölüm IV — Callable, Future ve arkadaşlar - 3CompletableStageCompletableStageCompletableFuture

CompletableFuture.completedFuture(2L)
				 .thenApply((a) -> {
					throw new IllegalStateException("error");
				 }).thenApply((a) -> 3L)
				 //.exceptionally(ex -> 0L)
				 .thenAccept(val -> System.out.println(val));
Bu kod hiçbir şey yapmayacak çünkü bir istisna olacak ve başka hiçbir şey olmayacak. Ancak "istisnai olarak" ifadesinin açıklamasını kaldırarak beklenen davranışı tanımlarız. Bahsetmişken CompletableFuture, aşağıdaki videoyu da izlemenizi tavsiye ederim: Naçizane fikrime göre bunlar internetteki en açıklayıcı videolar arasında. Tüm bunların nasıl çalıştığını, hangi araç setine sahip olduğumuzu ve tüm bunlara neden ihtiyaç duyulduğunu netleştirmeleri gerekir.

Çözüm

Umarım, tamamlandıktan sonra hesaplamaları almak için iş parçacıklarını nasıl kullanabileceğin artık açıktır. Ek malzeme: Birlikte daha iyi: Java ve Thread sınıfı. Bölüm I — Yürütme konuları Birlikte daha iyi: Java ve Thread sınıfı. Bölüm II — Eşitleme Birlikte daha iyi: Java ve Thread sınıfı. Bölüm III — Etkileşim Birlikte Daha İyi: Java ve Thread sınıfı. Bölüm V — Yürütücü, ThreadPool, Fork/Join Birlikte daha iyi: Java ve Thread sınıfı. Bölüm VI - Ateş edin!
Yorumlar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION