concurrency java semaphore
Bu öğreticide, Eşzamanlılığı Java'da uygulamak için Java Semaphore, Executor Framework, ExecutorService gibi java.util.concurrent paketinin bileşenleri tartışılacaktır:
Önceki Java eğitimlerimizden, Java platformunun eşzamanlı programlamayı sıfırdan desteklediğini biliyoruz. Eşzamanlılığın temel birimi bir iş parçacığıdır ve Java'da iş parçacıkları ve çoklu okumayı ayrıntılı olarak tartıştık.
Java 5'ten itibaren, Java platformuna 'java.util.concurrent' adlı bir paket eklendi. Bu paket, programcının eşzamanlı (çok iş parçacıklı) uygulamalar geliştirmesini kolaylaştıran bir dizi sınıf ve kitaplık içerir. Bu paketi kullanarak, eşzamanlı kavramların çoğunun hazır uygulamalarına sahip olduğumuz için karmaşık sınıflar yazmak zorunda değiliz.
=> TÜM Java Öğreticilerini Buradan Kontrol Edin.
Bu eğitimde, Java'da eşzamanlılık ve çoklu okuma ile ilgili java.util.concurrent paketinin çeşitli bileşenlerini tartışacağız.
Ne öğreneceksin:
java.util.concurrent Paketi
Aşağıda, Java'da eşzamanlılık ve çoklu okuma ile ilgili java.util.concurrent paketinin çeşitli bileşenleri listelenmiştir. Basit programlama örnekleri yardımıyla her bileşeni ayrıntılı olarak inceleyelim. Yapacağımız bileşenlerden bazıları
tartışmak:
- Yürütme çerçevesi
- Yürütme Hizmeti
- İplik Havuzu
- Aranabilir
- Kilitler- ReentrantLock
- Semafor
- ForkJoinHavuzu
Java'da Yürütme Çerçevesi
Java'daki Executor Framework, JDK 5 sürümüyle birlikte piyasaya sürüldü. Executor Framework (java.util.concurrent.Executor), birden çok iş parçacığını verimli bir şekilde işlememize yardımcı olan bileşenlerden oluşan bir çerçevedir.
Executor Framework'ü kullanarak, mevcut olan evreleri yeniden kullanarak Çalıştırılabilir olan nesneleri çalıştırabiliriz. Nesneleri çalıştırmamız gerektiğinde her seferinde yeni evreler oluşturmamıza gerek yoktur.
Executor API, bir görevin yürütülmesini gerçek görevden ayırır veya ayrıştırır. Cellat . Bir yürütücü, Yürütücü arayüzünde merkezlenmiştir ve alt arayüzlere, yani Yürütme Hizmeti ve sınıf ThreadPoolExecutor.
Bu nedenle, Executor'u kullanarak, Runnable nesneler oluşturmalı ve ardından onları çalıştıran yürütücüye göndermeliyiz.
Executor çerçevesini kullanırken izlenecek en iyi uygulamalardan bazıları şunlardır:
- En iyi listeleri gözden geçirmek için bir kodu çapraz kontrol edip planlamalıyız, böylece koddaki kilitlenmeyi ve canlı kilitlenmeyi tespit edebiliriz.
- Java kodu her zaman statik analiz araçlarına karşı yürütülmelidir. Örnekler statik analiz araçları arasında FindBugs ve PMD bulunmaktadır.
- Yalnızca istisnaları değil, çok iş parçacıklı programlardaki hataları da yakalamalıyız.
Şimdi Java'daki Executor Framework bileşenlerini tartışalım.
Cellat
Yürütücü, kendisine sağlanan görevleri yürüten bir nesneyi temsil etmek için kullanılan bir arayüz olarak tanımlanabilir. Görevin geçerli iş parçacığı üzerinde mi yoksa yeni iş parçacığı üzerinde mi çalıştırılacağı, çağrının başlatıldığı noktaya bağlıdır ve bu da uygulamaya bağlıdır.
Böylece, Executor'u kullanarak, görevleri gerçek görevden ayırabilir ve ardından eşzamansız olarak çalıştırabiliriz.
Ancak, Executor kullanarak görevin yürütülmesinin eşzamansız olması gerekmez. Yürütücüler ayrıca iş parçacığını çağırarak görevi anında başlatabilir.
Aşağıda, Executor örneği oluşturmak için örnek bir kod parçası verilmiştir:
public class Invoker implements Executor { @Override public void execute (Runnable r_interface) { r_interface.run(); } }
Yukarıda gösterildiği gibi çağırıcı oluşturulduktan sonra, onu aşağıdaki gibi görevi yürütmek için kullanabiliriz.
public void execute () { Executor executor = new Invoker (); executor.execute ( () -> { //perform task }); }
Görev Yürütücü tarafından kabul edilmediyse, RejectExecutionException oluşturur.
Yürütme Hizmeti
Bir ExecutorService (java.util.concurrent.ExecutorService), gönderilen görevleri iş parçacıklarının kullanılabilirliğine göre zamanlar ve ayrıca bir bellek kuyruğu tutar. ExecutorService, görevlerin Eşzamansız olarak işlenmesi için eksiksiz bir çözüm görevi görür.
ExecutorService'i kodda kullanmak için bir Runnable sınıfı oluşturuyoruz. ExecutorService bir iş parçacığı havuzunu korur ve ayrıca görevleri iş parçacıklarına atar. İş parçacığının mevcut olmaması durumunda görevler de sıraya girebilir.
Aşağıda basit bir ExecutorService örneği verilmiştir.
import java.util.concurrent.*; public class Main { public static void main(String() args) { //create ExecutorService instance with 10 threads ExecutorService executor_Service = Executors.newFixedThreadPool(10); //assign the service to Runnable instance executor_Service.execute(new Runnable() { @Override public void run() { //print the message System.out.println('Simple Example of ExecutorService!!!'); } }); //shutdown executorService executor_Service.shutdown(); } }
Çıktı
Yukarıdaki programda, 10 iş parçacığından oluşan bir iş parçacığı havuzuna sahip basit bir ExecutorService örneği oluşturuyoruz. Daha sonra Runnable örneğine atanır ve yukarıdaki mesajı yazdırmak için yürütülür. Mesajı yazdırdıktan sonra, ExecutorService kapatılır.
Konu Havuzu
Java'daki İş Parçacığı havuzu, birçok kez yeniden kullanılabilen ve görevlere atanabilen bir grup çalışan iş parçacıklarından oluşur.
Bir iş parçacığı havuzu, sabit boyutlu bir iş parçacığı grubu içerir. Her iş parçacığı iş parçacığı havuzundan çıkarılır ve hizmet sağlayıcı tarafından bir görev atanır. Atanan iş tamamlandığında, iş parçacığı yeniden iş parçacığı havuzuna verilir.
İş parçacığı havuzu, görev her kullanılabilir olduğunda yeni bir iş parçacığı oluşturmak zorunda olmadığımız için avantajlıdır, bu nedenle performans artar. İstekleri işlemek için iş parçacığı havuzlarının kullanıldığı Servlet ve JSP kullanan gerçek zamanlı uygulamalarda kullanılır.
Çok iş parçacıklı uygulamalarda, İş Parçacığı Havuzu kaynakları korur ve paralelliği önceden tanımlanmış sınırlar içinde tutmaya yardımcı olur.
Aşağıdaki Java programı, Java'daki İş Parçacığı havuzunu göstermektedir.
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; class WorkerThreadClass implements Runnable { private String message; //thread class constructor public WorkerThreadClass(String s){ this.message=s; } //run method for thread public void run() { System.out.println(' Start: '+message); processmessage(); //sleep between start and end System.out.println(' End: '+ message); } //processmessage method => sleeps the thread for 2 sec private void processmessage() { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } public class Main { public static void main(String() args) { //create a ExecutorService instance ExecutorService executor = Executors.newFixedThreadPool(5);//creating a pool of 5 threads //create thread instances and execute them for (int i = 0; i <5; i++) { Runnable workerThrd = new WorkerThreadClass('Thread_' + i); executor.execute(workerThrd);//calling execute method of ExecutorService } //shutdown ExecutorService executor.shutdown(); while (!executor.isTerminated()) { } System.out.println('Finished all threads'); } }
Çıktı
Yukarıdaki programlarda, “newFixedThreadPool” metodu kullanılarak oluşturulan 5 iş parçacığından oluşan bir iş parçacığı havuzu bulunmaktadır. Daha sonra iş parçacıkları oluşturulur ve havuza eklenir ve yürütme için ExecutorService'e atanır.
Java'da Çağrılabilir
İki yaklaşım kullanarak iş parçacığı oluşturabileceğimizi zaten biliyoruz. Bir yaklaşım, Thread sınıfını genişletmek, ikinci yaklaşım ise Runnable bir arayüz uygulamaktır.
Bununla birlikte, Runnable arayüzü kullanılarak oluşturulan iş parçacıkları bir özellikten yoksundur, yani iş parçacığı sonlandırıldığında veya run () yürütmeyi tamamladığında bir sonuç döndürmez. Bu, Çağrılabilir arayüzün resmin içine girdiği yerdir.
Çağrılabilir bir arabirim kullanarak, bir sonuç döndürmesi için bir görevi tanımlarız. Ayrıca bir istisna da oluşturabilir. Çağrılabilir arabirim, java.util.concurrent paketinin bir parçasıdır.
Windows 10 için en iyi ücretsiz kayıt defteri temizleyici
Callable arabirim, call () yönteminin bir değer döndürmesi ve denetlenen istisna atması arasındaki tek farkla Runnable arabirimi tarafından sağlanan run () yöntemiyle benzer satırlarda olan bir call () yöntemi sağlar.
Callable arayüzünün call () yöntemi aşağıdaki prototipe sahiptir.
public Object call () throws Exception;
Call () yöntemi bir Object döndürdüğünden, ana iş parçacığı bunun farkında olmalıdır.
Dolayısıyla, dönüş değeri ana iş parçacığı tarafından bilinen başka bir nesnede saklanmalıdır. Bu amaca bir 'Gelecek' nesnesi kullanılarak hizmet edilir. Gelecek nesne, bir iş parçacığı tarafından döndürülen sonucu tutan bir nesnedir. Veya başka bir deyişle, Callable döndüğünde sonucu tutacaktır.
Callable, başka bir iş parçacığında çalışması gereken bir görevi kapsüller. Gelecek nesnesi, farklı bir iş parçacığından döndürülen sonucu depolar.
Çağrılabilir bir arabirim, bir iş parçacığı oluşturmak için kullanılamaz. Bir iş parçacığı oluşturmak için Runnable'a ihtiyacımız var. Ardından sonucu saklamak için bir Gelecek nesnesi gereklidir. Java, hem Runnable hem de Future'ı uygulayarak işlevselliği birleştiren 'FutureTask' adlı somut bir tür sağlar.
Callable ile bir kurucu sağlayarak bir FutureTask oluşturuyoruz. Bu FutureTask nesnesi daha sonra bir Thread nesnesi oluşturmak için Thread sınıfının yapıcısına verilir.
Aşağıda, Çağrılabilir arayüzü ve Gelecek nesnesini gösteren bir Java programı verilmiştir. Bu programda FutureTask nesnesini de kullanıyoruz.
Daha önce bahsedildiği gibi, programda, geçersiz kılınmış bir call () yöntemi ile bir Callable arabirimi uygulayan bir sınıf yaratıyoruz. Ana yöntemde 10 FutureTask nesnesi oluşturuyoruz. Her nesne yapıcısının bağımsız değişkeni olarak Çağrılabilir bir sınıf nesnesi vardır. Sonra FutureTask nesnesi bir iş parçacığı örneğiyle ilişkilendirilir.
Dolayısıyla dolaylı olarak Çağrılabilir arayüz nesnesi kullanarak bir iş parçacığı oluştururuz.
import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; //create a class implementing Callable interface class CallableDemo implements Callable { //define call () method public Object call() throws Exception { Random generator = new Random(); Integer randomNumber = generator.nextInt(10); Thread.sleep(randomNumber * 1000); return randomNumber; } } public class Main { public static void main(String() args) throws Exception { // Array of FutureTask objects FutureTask() randomNumberTasks = new FutureTask(10); for (int i = 0; i <10; i++) { Callable callable = new CallableDemo(); // Create the FutureTask with Callable class randomNumberTasks(i) = new FutureTask(callable); // create thread with FutureTask Thread t = new Thread(randomNumberTasks(i)); //start the thread t.start(); } System.out.println('The contents of FutureTask objects:'); for (int i = 0; i < 10; i++) { // get() contents of FutureTask System.out.print(randomNumberTasks(i).get() + ' '); } } }
Çıktı
Yukarıdaki programda gösterildiği gibi, Callable'ı uygulayan sınıfta geçersiz kılınan Callable'ın call () yöntemi rastgele sayılar üretir. İş parçacığı başlatıldığında, bu rastgele sayıları görüntüler.
Ayrıca, ana işlevde FutureTask nesnelerini kullanıyoruz. Gelecek arayüzünü uyguladığından, sonuçları Thread nesnelerinde saklamamız gerekmez. Benzer şekilde, görevi iptal edebilir, çalışıp çalışmadığını veya tamamlanıp tamamlanmadığını kontrol edebilir ve ayrıca FutureTask nesnesini kullanarak sonucu alabiliriz.
Java'da ReentrantLock
Senkronize anahtar kelimeyi kullanarak iş parçacığı senkronizasyonunu son eğitimimizde ayrıntılı olarak tartıştık. İş parçacığı senkronizasyonu için senkronize edilmiş kelimenin kullanımı temel yöntemdir ve bir şekilde katıdır.
Senkronize anahtar kelimeyi kullanarak bir iş parçacığı yalnızca bir kez kilitlenebilir. Ayrıca, bir iş parçacığı senkronize bloktan çıktıktan sonra, sonraki iş parçacığı kilidi alır. Bekleme kuyruğu yok. Bu sorunlar, uzun süre kaynaklara erişemeyebileceği için başka bir iş parçacığının aç kalmasına neden olabilir.
Bu sorunları çözmek için, iş parçacıklarını senkronize etmek için esnek bir yönteme ihtiyacımız var. 'Reentrant Locks', Java'da çok daha fazla esneklikle senkronizasyon sağlayan bu yöntemdir.
'ReentrantLock' sınıfı, Reentrant kilitlerini uygular ve 'import java.util.concurrent.locks' paketinin bir parçasıdır. ReentrantLock sınıfı, paylaşılan kaynaklara erişmek için yöntem senkronizasyonu sağlar. Sınıflar, iş parçacıkları tarafından erişildiğinde kaynakları kilitlemek / kilidini açmak için kilitleme ve kilit açma yöntemlerine de sahiptir.
ReentrantLock'un tuhaf bir özelliği, iş parçacığının paylaşılan kaynağı ReentrantLock kullanarak birden fazla kez kilitleyebilmesidir. İş parçacığı kaynağı kilitlediğinde bire ayarlanan tutma sayısı sağlar.
İş parçacığı, kilidi açmadan önce kaynağa yeniden girebilir ve erişebilir. Evre, Reentrant kilidini kullanarak kaynağa her eriştiğinde, bekletme sayısı bir artar. Her kilit açma için bekletme sayısı bir azaltılır.
Bekletme sayısı 0'a ulaştığında, paylaşılan kaynağın kilidi açılır.
ReentrantLock sınıfı ayrıca, kilidin yapıcısı ile iletilebilen bir boole değeri olan bir adillik parametresi sağlar. Adillik parametresi doğru olarak ayarlandığında, o zaman bir iş parçacığı kilidi serbest bıraktığında, kilit bekleyen en uzun iş parçacığına geçer. Bu açlığı önler.
Reentrant kilitleri aşağıdaki şekilde kullanılabilir:
return_type method_name() { reentrantlock.lock(); try { //Do some work } catch(Exception e) { e.printStackTrace(); } finally { reentrantlock.unlock(); } }
ReentrantLock için unlock deyiminin her zaman last bloğunda olduğuna dikkat edin. Bu, bir istisna atılsa bile kilidin serbest kalmasını garanti eder.
ReentrantLock'u anlamak için bir Java programı uygulayalım.
import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.*; import java.util.concurrent.locks.ReentrantLock; //thread class that implements Runnable interface class ThreadClass implements Runnable { String task_name; //define ReentrantLock object ReentrantLock thrd_lck; //ThreadClass constructor initialized lock and task name public ThreadClass(ReentrantLock r_lock, String t_name) { thrd_lck = r_lock; task_name = t_name; } //thread run () method public void run() { boolean bool_val = false; while (!bool_val) { //check for Outer Lock boolean tryLock_val = thrd_lck.tryLock(); // if lock is free, do the following if(tryLock_val) { try { for(int i=0;i<=6;i++) { if(i>=2) { thrd_lck.lock(); Thread thread_one = new Thread(); System.out.println('Thread Created.....'); if(i==3) { thread_one.setName('Maint Thread2'); System.out.println('Thread Created.....'); } } if(i==4) thrd_lck.unlock(); break; } System.out.println('ReentrantLock=>Is locked after sleep(1500) : ' + thrd_lck.isLocked()); System.out.println('Work done for task : ' + task_name ); bool_val = true; } catch(Exception e) { e.printStackTrace(); } } } } } public class Main { public static void main(String() args) { //define ReentrantLock lock object and service pool ReentrantLock reentrant_lock = new ReentrantLock(); ExecutorService pool = Executors.newFixedThreadPool(2); //create thread instance and pass lock and task name Runnable worker_thread = new ThreadClass(reentrant_lock, 'ThreadJob'); //execute the thread in exec pool pool.execute(worker_thread); //shut down the pool pool.shutdown(); } }
Çıktı
Yukarıdaki programda bir iş parçacığı oluşturduk ve bunun için ReentrantLock kullandık. ReentrantLock kullanılarak paylaşılan kaynağa erişilebilir.
Java'da Semafor
İş parçacığı senkronizasyonunun sonraki yöntemi Semafor kullanmaktır. Semafor adı verilen bu yapıyı kullanarak, paylaşılan bir kaynağa erişim bir sayaç aracılığıyla kontrol edilir. İş parçacığı arasında sinyaller gönderilir, böylece kritik bölümü koruyabiliriz ve ayrıca kaçırılan sinyalleri önleyebiliriz.
Semafor, bu süreçleri senkronize ederek eşzamanlı süreçleri yönetmek için kullanılan bir değişken olarak tanımlanabilir. Semaforlar ayrıca paylaşılan kaynağa erişimi senkronize etmek ve böylece bir yarış durumundan kaçınmak için kullanılır. Semafor tarafından paylaşılan kaynağa erişim için bir iş parçacığına verilen izin, izin olarak da adlandırılır.
Hangi işlevleri yerine getirdiklerine bağlı olarak semaforlar iki türe ayrılabilir:
# 1) İkili Semafor: Bir ikili semafor, eşzamanlı süreçleri senkronize etmek ve karşılıklı dışlamayı uygulamak için kullanılır. İkili bir semafor yalnızca iki değeri varsayar, yani 0 ve 1.
# 2) Semafor Sayma: Sayma semaforu, kritik bölüme girebilecek işlemlerin sayısını gösteren bir değere sahiptir. Değer, herhangi bir noktada kritik bölüme giren maksimum işlem sayısını gösterir.
Peki bir Semafor nasıl çalışır?
Bir Semaforun çalışması aşağıdaki adımlarla özetlenebilir:
- Semafor sayısı> 0 ise, iş parçacığının kritik bölüme erişim iznine sahip olduğu ve ardından sayının azaldığı anlamına gelir.
- Aksi takdirde, izin alınana kadar iş parçacığı bloke edilir.
- İş parçacığı paylaşılan kaynağa erişim ile tamamlandığında, izin serbest bırakılır ve semafor sayısı artırılır, böylece başka bir iş parçacığı yukarıdaki adımları tekrarlayabilir ve izni alabilir.
Semaforların çalışmasının yukarıdaki adımları aşağıdaki akış şemasında özetlenebilir.
Java'da, semaforumuzu uygulamamıza gerek yoktur, ancak bir Semafor semafor işlevselliğini uygulayan sınıf. Semafor sınıfı, java.util.concurrent paketi.
Semaphore sınıfı, semafor nesnesini oluşturabileceğimiz aşağıdaki yapıcıları sağlar:
Semaphore (int num_value) Semaphore (int num_value, boolean how)
Buraya,
num_value => paylaşılan kaynağa erişebilen iş parçacığı sayısını belirleyen izin sayısının başlangıç değeri.
nasıl => evrelerin izin verileceği sırayı ayarlar (nasıl = doğru). Eğer nasıl = yanlış ise, o zaman böyle bir sıra izlenmez.
Şimdi, paylaşılan kaynak erişimini yönetmek ve yarış durumunu önlemek için kullanılan Semaforu gösterecek bir Java programı uygulayacağız.
import java.util.concurrent.*; //class for shared resource class SharedRes { static int count = 0; } class ThreadClass extends Thread { Semaphore sem; String threadName; public ThreadClass(Semaphore sem, String threadName) { super(threadName); this.sem = sem; this.threadName = threadName; } @Override public void run() { // Thread T1 processing if(this.getName().equals('T1')) { System.out.println('Start: ' + threadName); try { System.out.println(threadName + ' :waiting for a permit.'); // acquire the permit sem.acquire(); System.out.println(threadName + ':Acquired permit'); // access shared resource for(int i=0; i <5; i++) { SharedRes.count++; System.out.println(threadName + ': ' + SharedRes.count); Thread.sleep(10); } } catch (InterruptedException exc) { System.out.println(exc); } // Release the permit. System.out.println(threadName + ':Released the permit'); sem.release(); } // Thread T2 processing else { System.out.println('Start: ' + threadName); try { System.out.println(threadName + ':waiting for a permit.'); // acquire the lock sem.acquire(); System.out.println(threadName + ':Acquired permit'); // process the shared resource for(int i=0; i < 5; i++) { SharedRes.count--; System.out.println(threadName + ': ' + SharedRes.count); Thread.sleep(10); } } catch (InterruptedException exc) { System.out.println(exc); } // Release the permit. System.out.println(threadName + ':Released the permit.'); sem.release(); } } } public class Main { public static void main(String args()) throws InterruptedException { //create Semaphore=> #permits = 1 Semaphore sem = new Semaphore(1); // Create thread instances T1 & T2 //T1=> Increments the count; T2=> Decrements the count ThreadClass thread1 = new ThreadClass(sem, 'T1'); ThreadClass thread2 = new ThreadClass(sem, 'T2'); // start T1 & T2 thread1.start(); thread2.start(); // Wait T1 & T2 thread1.join(); thread2.join(); System.out.println('count: ' + SharedRes.count); // display final count. } }
Çıktı
Bu program, paylaşılan kaynak için bir sınıf ilan etti. Ayrıca, sınıf yapıcısında başlatılan bir semafor değişkenine sahip olduğumuz bir iş parçacığı sınıfını da bildirir.
Thread sınıfının geçersiz kılınan run () yönteminde, iş parçacığının izni aldığı, paylaşılan bir kaynağa eriştiği ve ardından izni serbest bıraktığı iş parçacığı örneğinin işlenmesi gerçekleştirilir.
Ana yöntemde, iki iş parçacığı örneği bildirdik. Her iki iş parçacığı daha sonra başlatılır ve ardından birleştirme yöntemini kullanarak beklerler. Son olarak, sayı görüntülenir, yani 0, her iki iş parçacığının paylaşılan kaynakla bittiğini gösterir.
Java'da Çatal Ve Katıl
Çatal / birleştirme çerçevesi ilk olarak Java 7'de tanıtıldı. Bu çerçeve, paralel işlemeyi hızlandırabilen araçlardan oluşur. Sistemdeki mevcut tüm işlemci çekirdeklerini kullanır ve görevi tamamlar. Çatal / birleştirme çerçevesi böl ve yönet yaklaşımını kullanır.
Fork / Join çerçevesinin arkasındaki temel fikir, ilk çerçeve olan 'Forks' un, yani görevler atomik olana kadar görevi özyinelemeli olarak daha küçük bireysel alt görevlere bölerek asenkron olarak yürütülebilmeleridir.
Bunu yaptıktan sonra, görevler 'birleştirilir', yani tüm alt görevler tek bir göreve veya dönüş değerine yinelemeli olarak birleştirilir.
Çatal / birleştirme çerçevesi, 'ForkJoinPool' olarak bilinen bir iş parçacığı havuzuna sahiptir. Bu havuz, 'ForkJoinWorkerThread' tipi çalışan iş parçacığını yöneterek etkili paralel işleme sağlar.
ForkJoinPool, çalışan iş parçacıklarını yönetir ve ayrıca iş parçacığı havuzu performansı ve durumu hakkında bilgi almamıza yardımcı olur. ForkJoinPool, yukarıda tartıştığımız 'Yürütme Hizmeti' nin bir uygulamasıdır.
Çalışan iş parçacıklarının aksine, ForkJoinPool her alt görev için ayrı bir iş parçacığı oluşturmaz. ForkJoinPool'daki her iş parçacığı, görevleri depolamak için sırasını (çift uçlu kuyruğu) korur.
Deque, iş parçacığının iş yükü dengeleme işlevi görür ve bunu aşağıda açıklanan bir 'iş çalma algoritması' yardımıyla yapar.
İş Çalma Algoritması
İş çalma algoritmasını basit kelimelerle şu şekilde tanımlayabiliriz: 'Bir ileti dizisi ücretsiz ise, işi meşgul ileti dizilerinden' çalın '.
Bir işçi iş parçacığı, görevleri her zaman sırasından alır. Deque'teki tüm görevler bittiğinde ve ardışık boş olduğunda, çalışan iş parçacığı başka bir dizinin kuyruğundan veya 'genel giriş kuyruğundan' bir görev alacaktır.
Bu şekilde, görevler için rekabet eden iş parçacığı olasılığı en aza indirilir ve ayrıca iş parçacığının iş için keşif yapması gereken sayısı da azalır. Bunun nedeni, iş parçacığının halihazırda mevcut işin en büyük parçasına sahip olması ve onu bitirmesidir.
Peki, ForkJoinPool'u bir programda nasıl kullanabiliriz?
ForkJoinPool'un genel tanımı aşağıdaki gibidir:
public class ForkJoinPool extends AbstractExecutorService
ForkJoinPool sınıfı, 'java.util.concurrent' paketinin bir parçasıdır.
Java 8'de, ortak havuza veya varsayılan iş parçacığı havuzuna bir başvuru sağlayan 'common-pool ()' statik yöntemini kullanarak ForkJoinPool'un bir örneğini oluşturuyoruz.
ForkJoinPool commonPool = ForkJoinPool.commonPool ();
Java 7'de, bir ForkJoinPool örneği oluşturuyoruz ve bunu aşağıda gösterildiği gibi fayda sınıfı alanına atıyoruz.
public static ForkJoinPool forkJoinPool = new ForkJoinPool(2);
Yukarıdaki tanım, havuzun 2 işlemci çekirdeği kullanacağı şekilde havuzun paralellik düzeyinin 2 olduğunu belirtir.
Yukarıdaki havuza erişmek için aşağıdaki ifadeyi verebiliriz.
ForkJoinPool forkJoinPool = PoolUtil.forkJoinPool;
ForkJoinPool görevleri için temel tür 'ForkJoinTask' tır. Alt sınıflarından birini, yani void görevler için RecursiveAction ve bir değer döndüren görevler için RecursiveTask'ı genişletmeliyiz. Her iki genişletilmiş sınıf da, içinde görevin mantığını tanımladığımız soyut bir yöntem compute () sağlar.
Aşağıda, ForkJoinPool'u göstermek için bir örnek verilmiştir.
import java.util.ArrayList; import java.util.List; import java.util.concurrent.*; //class declaration for ForkJoinPool tasks class FJPoolTask extends RecursiveAction { private long Load = 0; public FJPoolTask(long Load) { this.Load = Load; } @Override protected void compute() { //if threshold is reached, break tasks into smaller tasks List subtasks = new ArrayList(); subtasks.addAll(createSubtasks()); for(RecursiveAction subtask : subtasks){ subtask.fork(); } } //create subtasks private List createSubtasks() { List sub_tasks =new ArrayList(); FJPoolTask sub_task1 = new FJPoolTask(this.Load / 2); FJPoolTask sub_task2 = new FJPoolTask(this.Load / 2); FJPoolTask sub_task3 = new FJPoolTask(this.Load / 2); sub_tasks.add(sub_task1); sub_tasks.add(sub_task2); sub_tasks.add(sub_task3); return sub_tasks; } } public class Main { public static void main(final String() arguments) throws InterruptedException { //get count of available processors int proc = Runtime.getRuntime().availableProcessors(); System.out.println('Processors available:' +proc); //declare forkJoinPool ForkJoinPool Pool = ForkJoinPool.commonPool(); System.out.println(' Active Threads (Before invoke):' +Pool.getActiveThreadCount()); //Declare ForkJoinPool task object FJPoolTask t = new FJPoolTask(400); //submit the tasks to the pool Pool.invoke(t); System.out.println(' Active Threads (after invoke):' +Pool.getActiveThreadCount()); System.out.println('Common Pool Size :' +Pool.getPoolSize()); } }
Çıktı
Yukarıdaki programda, 'invoke ()' yöntemini çağırmadan önce ve sonra sistemdeki aktif evre sayısını buluyoruz. İnvoke () yöntemi, görevleri havuza göndermek için kullanılır. Ayrıca sistemdeki mevcut işlemci çekirdeklerinin sayısını da buluyoruz.
Sıkça Sorulan Sorular
S # 1) Java Util Concurrent nedir?
Cevap: 'Java.util.concurrent' paketi, eşzamanlı (çok iş parçacıklı) uygulamaların geliştirilmesini kolaylaştırmak için Java tarafından sağlanan bir sınıflar ve arabirimler kümesidir. Bu paketi kullanarak, sınıflarımızı yazmak zorunda kalmadan doğrudan arayüzü ve sınıfları ve API'leri kullanabiliriz.
S # 2) Aşağıdakilerden hangisi java.util'de bulunan eşzamanlı uygulamalardır. eşzamanlı paket?
Cevap: Yüksek düzeyde, java.util.concurrent paketi Yürütücüler, Eşzamanlayıcılar, Kuyruklar, Zamanlamalar ve Eşzamanlı Koleksiyonlar gibi yardımcı programları içerir.
S # 3) Gelecekteki Java Nedir?
Cevap: Bir Gelecek nesnesi (java.util.concurrent.Future), Çağrılabilir arabirim uygulandığında bir iş parçacığı tarafından döndürülen sonucu depolamak için kullanılır.
S # 4) Java'da iş parçacığı güvenli nedir?
Cevap: Java'daki iş parçacığı güvenli bir kod veya sınıf, çok iş parçacıklı veya eşzamanlı bir ortamda herhangi bir sorun olmadan paylaşılabilen ve beklenen sonuçları üreten bir kod veya sınıftır.
S # 5) Java'da senkronize edilmiş koleksiyon nedir?
Cevap: Senkronize bir koleksiyon, iş parçacığı açısından güvenli bir koleksiyondur. Java.util.Collections sınıfının eşitlenmiş koleksiyonu () yöntemi, eşitlenmiş (iş parçacığı açısından güvenli) bir koleksiyon döndürür.
Sonuç
Bu eğitimle, Java'da çoklu iş parçacığı ve eşzamanlılık konusunu tamamladık. Daha önceki eğitimlerimizde çok iş parçacıklı okumayı ayrıntılı olarak tartıştık. Burada, java.util.concurrent paketinin bir parçası olan eşzamanlılık ve çoklu okuma ile ilgili eşzamanlılığı ve uygulamayı tartıştık.
Semafor ve ReentrantLock olmak üzere iki senkronizasyon yöntemini daha tartıştık. Görevleri daha basit görevlere bölerek ve sonunda sonuca katılarak yürütmek için kullanılan ForkJoinPool'u da tartıştık.
Java.util.concurrent paketi ayrıca iş parçacıkları yürütmemize yardımcı olan Executor çerçevesini ve çalıştırıcıları da destekler. Ayrıca, yürütme tamamlandığında havuza döndürülen yeniden kullanılabilir iş parçacıklarından oluşan iş parçacığı havuzu uygulamasını da tartıştık.
Elde edilen iş parçacığı sonucunu saklamak için kullanılan iş parçacığından ve Gelecek nesnesinden bir sonuç döndürmemize yardımcı olan Runnable'a benzer başka bir arayüzü tartıştık.
=> Basit Java Eğitim Serisine Buradan Dikkat Edin.
Önerilen Kaynaklar
- Thread.Sleep () - Örneklerle Java'da Thread Sleep () Yöntemi
- Java Dağıtımı: Java JAR Dosyasının Oluşturulması ve Yürütülmesi
- Java Temelleri: Java Sözdizimi, Java Sınıfı ve Temel Java Kavramları
- Java Sanal Makinesi: JVM, Java Uygulamasını Çalıştırmada Nasıl Yardımcı Olur?
- Java'da Erişim Değiştiriciler - Örneklerle Eğitim
- Java Senkronize Edildi: Java'da İş Parçacığı Senkronizasyonu Nedir?
- Yeni Başlayanlar İçin JAVA Eğitimi: 100+ Uygulamalı Java Video Eğitimi
- Java Tamsayı ve Örneklerle Java BigInteger Sınıfı