Intermediate and Terminal Operations. Aralarındakı fərq.

 Intermediate (aralıq) və terminal əməliyyatlar arasındakı əsas fərq ondan ibarətdir ki, intermediate əməliyyatlar son nəticə vermədən məlumatları axın halına gətirir, terminal əməliyyatları isə son nəticə və ya yan təsir yaradır, həmçinin intermediate əməliyyatların icrasına təkan verir.

Intermediate əməliyyatlar verilənləri çevirmək və yeni axın yaratmaq üçün istifadə olunur. Məlumat axını üzərində daha mürəkkəb əməliyyatları yerinə yetirmək üçün onları bir-birinə zəncirləmək olar. Intermediate əməliyyatlara misal olaraq   filter() , map() , flatMap(), distinct() və sorted() daxildir . Bu əməliyyatlar sonrakı emal üçün istifadə oluna bilən yeni axını qaytarır.

Terminal əməliyyatları, əksinə, son nəticə və ya yan təsir yaratmaq üçün istifadə olunur, həmçinin intermediate əməliyyatların icrasını işə salar. Terminal əməliyyatlarına misal olaraq  forEach(), toArray(), reduce(), collect(), anyMatch(), allMatch(), noneMatch(), findFirst(), findAny(), count(), min(), max (), forEachOrdered() və toCollection(). Bu əməliyyatlar axını istehlak edir və son nəticəni qaytarır və ya axının elementlərini konsola çap etmək kimi yan təsir yaradır.

Qeyd etmək vacibdir ki, terminal əməliyyatları axınların istehlak edildiyi nöqtədir, bundan sonra axın artıq istifadə edilə bilməz, ona görə də sonrakı əməliyyatları yerinə yetirmək üçün yeni axın yaratmalısınız.

Intermediate əməliyyatlar məlumatı çevirən, lakin yekun nəticə verməyən stream (axın) əməliyyatlardır. Məsələn, verilənlər axınında aralıq əməliyyat yalnız müəyyən elementləri daxil etmək üçün stream (axının) süzgəcdən keçirə bilər və ya elementləri yeni dəyərlərə uyğunlaşdıra bilər. Java-da Stream API-də   intermediate əməliyyata misal olaraq Predikatı qəbul edən və yalnız predikatı təmin edən elementləri ehtiva edən yeni axını qaytaran filter() metodudur.

Terminal əməliyyatları isə son nəticə və ya yan təsir yaradan əməliyyatlardır. Onlar stream (axının) sonunu qeyd edir və intermediate  əməliyyatların icrasına təkan verir. Java-da Stream API-də terminal əməliyyatına misal olaraq İstehlakçı götürən və onu stream (axının) hər bir elementə tətbiq edən forEach() metodudur.

Aralıq əməliyyatlar son nəticə vermədən məlumat stream (axını) çevirən əməliyyatlardır . Onlar stream (axın)-dakı verilənlər üzərində süzgəcdən keçirmək, xəritələşdirmək və ya digər əməliyyat növlərini yerinə yetirmək üçün istifadə oluna bilər və sonrakı emal üçün istifadə oluna bilən yeni stream (axını) qaytarır.  Intermediate əməliyyatların bəzi ümumi nümunələri bunlardır:

  • filter() : Bu əməliyyat Predikatı götürür və yalnız predikatı təmin edən elementləri ehtiva edən yeni stream (axını) qaytarır.

Məlumat  stream üzərində daha mürəkkəb əməliyyatları yerinə yetirmək üçün bu əməliyyatlar zəncirlə birləşdirilə bilər.  Məsələn,  müəyyən elementləri seçmək üçün filter() funksiyasından istifadə edə, sonra həmin elementləri çevirmək üçün map()  funksiyasından istifadə edə və nəticədə yaranan stream istehlak etmək üçün  forEach() funksiyasından istifadə edə bilərsiniz.

Java-da Stream API-də bəzi ümumi intermediate əməliyyatların siyahısı və onlardan istifadə nümunələri:

  • filter() : Bu əməliyyat Predikatı götürür və yalnız predikatı təmin edən elementləri ehtiva edən yeni  stream qaytarır. Məsələn, siz tam ədədlər stream (axının)-dan yalnız cüt ədədləri seçmək üçün filter() funksiyasından istifadə edə bilərsiniz:
Stream<Integer> evenNumbers = numbers.filter(n -> n % 2 == 0);
  • map() : Bu əməliyyat Funksiya götürür və onu stream (axın)-dakı hər bir elementə tətbiq edir, dəyişdirilmiş elementlərlə yeni stream qaytarır. Məsələn, sətir stream-ni onların uzunluqlu stream-nə çevirmək üçün map() funksiyasından istifadə edə bilərsiniz:
 Stream<Integer> stringLengths = strings.map(String::length);
  • flatMap() : Bu əməliyyat Funksiya götürür və onu stream-dəki hər bir elementə tətbiq edir, nəticədə cərəyanların birləşdirilmiş elementləri ilə yeni stream qaytarır. Məsələn, tam ədədlər siyahısı stream-ni düzləşdirmək üçün flatMap() funksiyasından istifadə edə bilərsiniz:
 Stream<Integer> flattened = lists.flatMap(List::stream);
  • distinct() : Bu əməliyyat onların təbii sırasına və ya təqdim edilmiş müqayisəçiyə əsaslanan fərqli elementləri olan yeni stream qaytarır. Məsələn, tam ədədlər  stream-dən dublikat elementləri silmək üçün distinct() istifadə edə bilərsiniz:
 Stream<Integer> distinct = integers.distinct();
  • sorted() : Bu əməliyyat təbii sıraya və ya təqdim edilmiş müqayisəçiyə görə çeşidlənmiş yeni stream qaytarır. Məsələn, sətir stream-ni əlifba sırası ilə çeşidləmək üçün sorted() funksiyasından istifadə edə bilərsiniz:
 Stream<String> sorted = strings.sorted();
  • peek() : Bu əməliyyat girişlə eyni elementləri olan yeni stream qaytaracaq, lakin hər bir element üçün təmin edilmiş istehlakçı tətbiq edəcək. Bu əməliyyat sazlama və monitorinq məqsədləri üçün faydalıdır, stream-i heç bir şəkildə dəyişməyəcək. Məsələn, tam ədədlər stream-nin hər bir elementini çap etmək üçün peek() funksiyasından istifadə edə bilərsiniz:
  Stream<Integer> peek = integers.peek(System.out::println);
  • limit() : Bu əməliyyat giriş  stream-dən verilən maksimum element sayından çox olmayan yeni stream qaytaracaq. Məsələn, tam ədədlər stream-nin ilk beş elementini əldə etmək üçün limit() funksiyasından istifadə edə bilərsiniz:
 Stream<Integer> limit = integers.limit(5);  
  • skip() : Bu əməliyyat ilk təmin edilmiş element sayından başqa giriş stream-nin bütün elementlərini ehtiva edən yeni stream qaytaracaq. Məsələn, tam ədədlər stream-nin ilk 10 elementini silmək üçün skip() funksiyasından istifadə edə bilərsiniz:
   Stream<Integer> skip = integers.skip(10); 

Bunlar Java-da Stream API-də  mövcud olan  intermediate   əməliyyatların bir neçə nümunəsidir , asOf, takeWhile, dropWhile, concat və s . kimi daha çox əməliyyatlar var. Məlumat stream-də daha mürəkkəb əməliyyatları yerinə yetirmək üçün bir neçə   intermediate  əməliyyatı birləşdirə bilərsiniz.

Java-da Stream API-də bəzi ümumi terminal əməliyyatlarının siyahısı və onlardan istifadə nümunələri:

  • forEach() : Bu əməliyyat İstehlakçı götürür və onu stream-dəki hər bir elementə tətbiq edir. Məsələn, sətir stream-nin hər bir elementini çap etmək üçün forEach() funksiyasından istifadə edə bilərsiniz:
simlər. forEach ( Sistem . out ::println);
  • toArray()Bu əməliyyat axının elementlərini ehtiva edən massivi qaytarır. Məsələn, sətirlər axınını sətirlər massivinə çevirmək üçün toArray() funksiyasından istifadə edə bilərsiniz:
String[] stringArray = strings.toArray(String[]::new);
  • reduce()Bu əməliyyat akkumulyator funksiyası və əlavə identifikasiya dəyərini götürür və onu tək bir dəyərə endirmək üçün axının elementlərinə tətbiq edir. Məsələn, tam ədədlər axınının cəmini tapmaq üçün reduce() istifadə edə bilərsiniz:
int sum = integers.reduce(0, Integer::sum);
  • collect() : Bu əməliyyat Kollektor götürür və onu List, Set və ya Map kimi vahid dəyərə endirmək üçün axının elementlərinə tətbiq edir. Məsələn, sətirlər axınını uzunluğuna görə qruplaşdırmaq üçün collect() funksiyasından istifadə edə bilərsiniz:
Map<Integer, List<String>> groupedByLength = strings.collect(Collectors.groupingBy(String::length)); 
  • anyMatch() : Bu əməliyyat axının hər hansı elementi verilmiş predikata uyğun gələrsə, true qaytarır, əks halda isə false qaytarır. Məsələn, sətir axınındakı hər hansı elementin “a” hərfi ilə başladığını yoxlamaq üçün anyMatch() funksiyasından istifadə edə bilərsiniz:
boolean  anyStartsWithA  = strings.anyMatch(s -> s.startsWith( "a" ));
  • allMatch() : Əgər axının bütün elementləri verilmiş predikata uyğun gələrsə, bu əməliyyat true qaytarır, əks halda isə false qaytarır. Məsələn, tam ədədlər axınındakı bütün elementlərin müsbət olub olmadığını yoxlamaq üçün allMatch() funksiyasından istifadə edə bilərsiniz:
boolean  allPositive  = integers.allMatch(i -> i > 0 );
  • noneMatch() : Əgər axının heç bir elementi verilmiş predikata uyğun gəlmirsə, bu əməliyyat true qaytarır, əks halda isə   false qaytarır. Məsələn, sətirlər axınındakı elementlərin heç birinin boş olmadığını yoxlamaq üçün noneMatch() funksiyasından istifadə edə bilərsiniz:
boolean noneEmpty = strings. noneMatch ( String :: isEmpty );
  • findFirst() : Bu əməliyyat bu axının ilk elementini təsvir edən Optional (könüllü) və ya axın boşdursa, boş Optional qaytarır. Məsələn, tam ədədlər axınının ilk elementini əldə etmək üçün findFirst() funksiyasından istifadə edə bilərsiniz:
  Optional<Integer> first = integers.findFirst();
  • findAny() : Bu əməliyyat axının bəzi elementlərini təsvir edən   Optional (könüllü) qaytarır və ya axın boşdursa, boş Optional   qaytarır. Məsələn, tam ədədlər axınının istənilən elementini əldə etmək üçün findAny() funksiyasından istifadə edə bilərsiniz:
  Optional<Integer> any = integers.findAny();
  • count() : Bu əməliyyat axındakı elementlərin sayını qaytarır. Məsələn, tam ədədlər axınındakı elementlərin sayını əldə etmək üçün count() funksiyasından istifadə edə bilərsiniz:
long count = integers.count(); 
  • min, max : Bu əməliyyat axının minimum və ya maksimum elementini onların təbii sırasına və ya təqdim edilmiş müqayisəçiyə uyğun olaraq qaytarır. O, minimum/maksimum elementdən ibarət Optional (könüllü)  və ya axın boşdursa, boş  Optional qaytarır. Məsələn,tam ədədlər axınının ən kiçik elementini əldə etmək üçün min() funksiyasından istifadə edə bilərsiniz:
  Optional<Integer> min = integers.min(Integer::compareTo);

və ya max() tam ədədlər axınının ən böyük elementini əldə etmək üçün:

 Optional<Integer> max = integers.max(Integer::compareTo); 
  • forEachOrdered() : Bu əməliyyat forEach() əməliyyatına bənzəyir, lakin o, elementlərin axın paralelləşdirildikdə belə, axında göründükləri ardıcıllıqla işlənəcəyinə zəmanət verir.
List < Integer > intList = integers.toCollection(ArrayList:: new );

Bunlar Java-da Stream API-də mövcud olan terminal əməliyyatlarının yalnız bir neçə nümunəsidir. Onlar intermediate əməliyyatların icrasına təkan verəcək və son nəticə və ya yan təsir yaradacaqlar.

Xülasə olaraq, intermediate əməliyyatlar verilənləri çevirmək və yeni axın yaratmaq üçün istifadə olunur, terminal əməliyyatları isə son nəticə və ya yan təsir yaratmaq üçün istifadə olunur, həmçinin intermediate əməliyyatların icrasını işə salar.

Differency between Multithreading and Concurrency || Multithreading və Concurrency arasındakı fərq

Multithreading Concurrency əlaqəli anlayışlardır, lakin eyni şey deyil.

Multithreading tək bir prosesin birdən çox thread-ə malik olma qabiliyyətinə aiddir. Bu thread-lər eyni vaxtda işləyə bilər, bu da prosesə eyni anda birdən çox işi yerinə yetirməyə imkan verir. Hər bir thread-in öz proqram sayğacı, stek və lokal dəyişənləri var, yəni onlar bir-birindən asılı olmayaraq işləyə bilirlər. Əməliyyat sistemi hər bir thread-ə CPU vaxt ayırır və onların eyni vaxtda işləməsinə imkan verir.

Multithreading proqramın işini yaxşılaşdırmaq üçün faydalıdır, çünki o, proqrama prosessorun çoxsaylı nüvələrindən istifadə etməyə imkan verir və eyni zamanda birdən çox işi yerinə yetirməyə imkan verməklə proqramın cavab qabiliyyətini yaxşılaşdıra bilər. O, həmçinin I/O bağlı və çox tapşırıqlı proqramların işini yaxşılaşdıra bilər.

O, əməliyyat sistemi tərəfindən planlaşdırıla bilən yüngül icra vahidləri olan thread-lərdən istifadə etməklə həyata keçirilir. Multithreading həm istifadəçi səviyyəsində, həm də nüvə səviyyəli thread-lərdə həyata keçirilə bilər,  birincisi tətbiqin özü tərəfindən həyata keçirilir və  ikincisi əməliyyat sistemi tərəfindən dəstəklənir.

Concurrency isə sistemin müxtəlif hissələrinin bir-birinə müdaxilə etmədən müstəqil işləmə qabiliyyətini ifadə edir. Eyni zamanda bir neçə tapşırığın icrasına imkan verir. Tapşırıqların eyni vaxtda yerinə yetirilməsinin yolu budur. Tapşırıqlar birdən çox prosessordan istifadə etməklə paralel olaraq yerinə yetirilə bilər və ya bir prosessorda qarışmış şəkildə icra edilə bilər. Paralelliyin əsas cəhəti odur ki, o, bir-birinin ardınca yerinə yetirməkdən fərqli olaraq, birdən çox tapşırığın üst-üstə düşən zaman dövrlərində irəliləyiş əldə etməyə imkan verir. 

Hesablamada concurrency proqramın və ya sistemin eyni vaxtda birdən çox işi yerinə yetirmək qabiliyyətinə aiddir. Bu, bir proqramın eyni vaxtda işləyən birdən çox icra başlığına malik ola biləcəyi və ya birdən çox prosesin müstəqil işləməsinə imkan vermək üçün mesaj ötürmə və ya paylaşılan yaddaşdan istifadə etməklə, multithreading kimi üsullarla əldə edilə bilər.

Concurrency vacibdir, çünki o, resurslardan səmərəli istifadə etməyə imkan verir, performansı yaxşılaşdırır və cavab verən, nasazlığa dözümlü sistemlər qurmağa imkan verir. O, həmçinin çoxlu maşınlar arasında paylana bilən kodu yazmağı asanlaşdırır və bu, yüksək miqyaslı sistemlərin inkişafına imkan verir.

Concurrency həyata keçirmək və əsaslandırmaq çətin ola bilər, çünki bu, sistemin müxtəlif hissələri arasında mürəkkəb qarşılıqlı əlaqəyə səbəb ola bilər və paylaşılan resurslara girişi idarə etmək üçün kilidlər semaforlar kimi sinxronizasiya mexanizmlərinin istifadəsini tələb edə bilər.

Xülasə, multithreading concurrency-ə nail olmağın bir yoludur, lakin concurrency digər vasitələrlə də əldə edilə bilər. Multithreading, birdən çox iş parçacığının bir proses daxilində yerinə yetirilməsinə imkan verən spesifik concurrency tətbiqidir, concurrency isə sistemin thread-lərdən istifadə edilib-edilməməsindən asılı olmayaraq, eyni vaxtda birdən çox işi idarə etmək qabiliyyətinə istinad edən daha ümumi bir anlayışdır. 


Threads in java || Java-da Thread-lar

Java-da thread eyni vaxtda icra vahididir. Çağırılan metodlar, onların arqumentləri və lokal dəyişənlər üçün öz çağırış yığını var. Hər bir proqramda ən azı bir thread var, əsas thread, proqram işə salındıqda JVM (Java Virtual Machine) tərəfindən yaradılır. Əsas thread ilə eyni vaxtda kodu işlətmək üçün əlavə thread-lar yarada bilərsiniz.

Yeni başlıq yaratmaq üçün ya Thread class-nı extends edib onun run() metodunu override edərek əlavə etmək, ya da class-ın yeni Thread-in   instance-nı yarada və ona  Runnable object  ötürə bilərsiniz. Sonuncu yanaşmadan istifadə edərək yeni başlıq yaradılma və başladılma nümunəsi:


public class MyThread implements Runnable { public void run() { // code to run in the new thread } } // ... MyThread thread = new MyThread(); Thread t = new Thread(thread); t.start();


Bir thread işə salındıqdan sonra, o, tətbiqdəki digər thread-ləri eyni vaxtda icra edilə bilər. Müəyyən edilmiş millisaniyələr üçün thread-i dayandırmaq üçün sleep() method-dan və ya davam etməzdən əvvəl mövzunun bitməsini gözləmək üçün join() method-dan istifadə edə bilərsiniz .

Java-da thread-lə işləmək üçün thread-in prioritetini təyin etmək və ya paylaşılan məlumatları qorumaq üçün synchronization  (sinxronizasiyadan) istifadə etmək kimi daha bir çox üsul və seçim var. 


Thread synchronization  (sinxronizasiyası) iki və ya daha çox paralel thread-in "critical section"(kritik bölmə) kimi tanınan xüsusi proqram bölməsini eyni vaxtda icra etməməsini təmin etmək prosesidir. Bu zəruridir, çünki paylaşılan resurslara eyni vaxtda giriş race condition (yarış şəraitinə ) və digər synchronization ( sinxronizasiya) problemlərinə səbəb ola bilər.

Java-da siz paylaşılan resurslara girişi sinxronlaşdırmaq üçün synchronized açar sözündən və monitorlardan istifadə edə bilərsiniz.

Java-da paylaşılan mənbəyə girişi sinxronlaşdırmaq üçün synchronized açar sözündən necə istifadə edə biləcəyiniz üçün nümunə :


public class Counter { private int count = 0; public synchronized void increment() { count++; } public synchronized int getCount() { return count; } } public class Main { public static void main(String[] args) { Counter counter = new Counter(); Thread t1 = new Thread(() -> { for (int i = 0; i < 1000; i++) { counter.increment(); } }); Thread t2 = new Thread(() -> { for (int i = 0; i < 1000; i++) { counter.increment(); } }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(counter.getCount()); } }


Bu misalda Counter class-ın paylaşılan resursu ( count field) və paylaşılan mənbəyə daxil olmaq üçün iki üsul ( increment() getCount()) var. Method increment()getCount() method həm synchronized açar sözlə qeyd olunur, bu da onları "critical section" (kritik bölmələr) edir. Bu, eyni anda bu üsullardan hər hansı birini yalnız bir thread-ın icra edə biləcəyini və count field atomik olaraq yenilənməsini təmin edir.

Əsas proqram eyni vaxtda count field-i artıran iki thread yaradır. Method sinxronlaşdırıldığı increment() üçün onu eyni anda yalnız bir thread yerinə yetirə bilər və count field təhlükəsiz şəkildə yenilənir. Hər iki thread tamamlandıqda count field-in son dəyəri çap olunur.

synchronized açar sözünə əlavə olaraq , Java həmçinin lock-lar, atomik dəyişənləri və java.util.concurrent paket kimi thread sinxronizasiyası üçün digər mexanizmləri də təmin edir.


Smart Presta spcookieslaw