Sobes.tech
Middle+

რა არის Fork/Join ჩარჩო და როგორ გამოიყენება Java-ში?

sobes.tech AI

პასუხი AI-სგან

Fork/Join ფრეიმვორკი — ეს არის სპეციალიზებული რეალიზაცია Executor ფრეიმვორკის, რომელიც განკუთვნილია ეფექტიანი პარალელიზაციისთვის, როდესაც დავალებები შეიძლება რეკურსიულად დაიშალოს უფრო პატარა ქვედავალებებად და შემდეგ გაერთიანდეს (join) მათი შედეგები. იგი დაფუძნებულია "გადანაწილება და მმართვა" (divide and conquer) პრინციპზე.

ძირითადი კომპონენტები:

  • ForkJoinPool: ნაკადების პული, რომელიც მართავს დავალებების შესრულებას. ის იყენებს "work-stealing" მექანიზმს, სადაც პულში მყოფი დამელოდებელი ნაკადები შეიძლება "გატაცონ" სხვა დაკავებული ნაკადებისგან დავალებები.
  • ForkJoinTask: აბსტრაქტული კლასი, რომელიც წარმოადგენს დავალებას, რომელიც შეიძლება შესრულდეს ამ ფრეიმვორკში. მას აქვს ორი ტიპი:
    • RecursiveAction: დავალება, რომელიც შედეგს არ აბრუნებს.
    • RecursiveTask<V>: დავალება, რომელიც აბრუნებს შედეგს ტიპის V.

გამოყენება:

  1. შექმნიან კლასს, რომელიც მემკვიდრეობს RecursiveAction ან RecursiveTask-ს.
  2. გადამწყვეტი ხდება compute() მეთოდის გადამწყვეტობა. ამ მეთოდში აღწერილია ლოგიკა:
    • თუ დავალება საკმარისად მცირეა (მცირე გარკვეულ ზღვარს), იგი პირდაპირ სრულდება.
    • წინააღმდეგ შემთხვევაში, დავალება ნაწილდება ქვედავალებებად, რომლებიც ასინქრონულად იშვიათად fork() მეთოდით.
    • შემდეგ ელოდებიან და იღებენ შედეგებს join() მეთოდით და აერთიანებენ მათ.
  3. შექმნიან ForkJoinPool-ის ობიექტს.
  4. დავალება გადაეცემა პულს invoke() ან submit() მეთოდით.

მაგალითი:

import java.util.concurrent.RecursiveTask;
import java.util.concurrent.ForkJoinPool;

class SumArrayTask extends RecursiveTask<Long> {
    private final long[] array;
    private final int start;
    private final int end;
    private static final int THRESHOLD = 1000; // ზღვარი

    public SumArrayTask(long[] array, int start, int end) {
        this.array = array;
        this.start = start;
        this.end = end;
    }

    @Override
    protected Long compute() {
        if (end - start <= THRESHOLD) {
            // პირდაპირი გამოთვლა მცირე დიაპაზონზე
            long sum = 0;
            for (int i = start; i < end; i++) {
                sum += array[i];
            }
            return sum;
        } else {
            // დავალების გაყოფა
            int mid = start + (end - start) / 2;
            SumArrayTask leftTask = new SumArrayTask(array, start, mid);
            SumArrayTask rightTask = new SumArrayTask(array, mid, end);

            // ასინქრონული ქვედავალების შესრულება
            leftTask.fork();
            // მარჯვენა ქვედავალების შესრულება მიმდინარე ან სხვა ხელმისაწვდომი ნაკადში
            Long rightResult = rightTask.compute();
            // ელოდება და იღებს მარცხენა ქვედავალების შედეგს
            Long leftResult = leftTask.join();

            // შედეგების გაერთიანება
            return leftResult + rightResult;
        }
    }
}

// გამოყენება ძირითად კოდში
// long[] data = new long[10000]; // მონაცემები
// // data-ის შევსება
// ForkJoinPool pool = new ForkJoinPool();
// long sum = pool.invoke(new SumArrayTask(data, 0, data.length));
// pool.shutdown(); // პულის დახურვა გამოყენების შემდეგ

უპირატესობები:

  • ავტომატური მართვა ნაკადების პულზე.
  • ეფექტიანი დატვირთვის განაწილება work-stealing-ის საშუალებით.
  • გაუმჯობესებული შესრულება "გადანაწილება და მმართვა" ტიპის დავალებებზე.

მოხერხებულობა:

  • არ არის შესაფერისი ყველა ტიპის პარალელური დავალებისთვის.
  • შეიძლება იყოს რთული განხორციელებაში შედარებით სხვა მიდგომებთან შედარებით მარტივ დავალებებზე.