ಕನ್ನಡ

ಜಾವಾದ ಫೋರ್ಕ್-ಜಾಯಿನ್ ಫ್ರೇಮ್‌ವರ್ಕ್‌ನ ಸಮಗ್ರ ಮಾರ್ಗದರ್ಶಿಯೊಂದಿಗೆ ಪ್ಯಾರಲಲ್ ಪ್ರೊಸೆಸಿಂಗ್‌ನ ಶಕ್ತಿಯನ್ನು ಅನ್ಲಾಕ್ ಮಾಡಿ. ನಿಮ್ಮ ಜಾಗತಿಕ ಅಪ್ಲಿಕೇಶನ್‌ಗಳಲ್ಲಿ ಗರಿಷ್ಠ ಕಾರ್ಯಕ್ಷಮತೆಗಾಗಿ ಕಾರ್ಯಗಳನ್ನು ವಿಭಜಿಸಲು, ಕಾರ್ಯಗತಗೊಳಿಸಲು ಮತ್ತು ಸಂಯೋಜಿಸಲು ಕಲಿಯಿರಿ.

ಪ್ಯಾರಲಲ್ ಟಾಸ್ಕ್ ಎಕ್ಸಿಕ್ಯೂಶನ್‌ನಲ್ಲಿ ಪ್ರಾವೀಣ್ಯತೆ: ಫೋರ್ಕ್-ಜಾಯಿನ್ ಫ್ರೇಮ್‌ವರ್ಕ್‌ನ ಒಂದು ಆಳವಾದ ನೋಟ

ಇಂದಿನ ಡೇಟಾ-ಚಾಲಿತ ಮತ್ತು ಜಾಗತಿಕವಾಗಿ ಸಂಪರ್ಕಗೊಂಡಿರುವ ಜಗತ್ತಿನಲ್ಲಿ, ದಕ್ಷ ಮತ್ತು ಸ್ಪಂದನಾಶೀಲ ಅಪ್ಲಿಕೇಶನ್‌ಗಳ ಬೇಡಿಕೆ ಅತಿಮುಖ್ಯವಾಗಿದೆ. ಆಧುನಿಕ ಸಾಫ್ಟ್‌ವೇರ್ ಆಗಾಗ್ಗೆ ಅಪಾರ ಪ್ರಮಾಣದ ಡೇಟಾವನ್ನು ಪ್ರಕ್ರಿಯೆಗೊಳಿಸಬೇಕು, ಸಂಕೀರ್ಣ ಲೆಕ್ಕಾಚಾರಗಳನ್ನು ನಿರ್ವಹಿಸಬೇಕು, ಮತ್ತು ಹಲವಾರು ಏಕಕಾಲೀನ ಕಾರ್ಯಾಚರಣೆಗಳನ್ನು ನಿಭಾಯಿಸಬೇಕಾಗುತ್ತದೆ. ಈ ಸವಾಲುಗಳನ್ನು ಎದುರಿಸಲು, ಡೆವಲಪರ್‌ಗಳು ಹೆಚ್ಚಾಗಿ ಪ್ಯಾರಲಲ್ ಪ್ರೊಸೆಸಿಂಗ್‌ಗೆ ಮುಖ ಮಾಡಿದ್ದಾರೆ – ಇದು ಒಂದು ದೊಡ್ಡ ಸಮಸ್ಯೆಯನ್ನು ಸಣ್ಣ, ನಿರ್ವಹಿಸಬಹುದಾದ ಉಪ-ಸಮಸ್ಯೆಗಳಾಗಿ ವಿಭಜಿಸುವ ಕಲೆಯಾಗಿದ್ದು, ಇವುಗಳನ್ನು ಏಕಕಾಲದಲ್ಲಿ ಪರಿಹರಿಸಬಹುದು. ಜಾವಾದ ಕನ್ಕರೆನ್ಸಿ ಯುಟಿಲಿಟಿಗಳ ಮುಂಚೂಣಿಯಲ್ಲಿ, ಫೋರ್ಕ್-ಜಾಯಿನ್ ಫ್ರೇಮ್‌ವರ್ಕ್ ಪ್ಯಾರಲಲ್ ಕಾರ್ಯಗಳ ಕಾರ್ಯಗತಗೊಳಿಸುವಿಕೆಯನ್ನು ಸರಳಗೊಳಿಸಲು ಮತ್ತು ಆಪ್ಟಿಮೈಜ್ ಮಾಡಲು ವಿನ್ಯಾಸಗೊಳಿಸಲಾದ ಒಂದು ಶಕ್ತಿಯುತ ಸಾಧನವಾಗಿ ಎದ್ದು ಕಾಣುತ್ತದೆ, ವಿಶೇಷವಾಗಿ ಕಂಪ್ಯೂಟ್-ಇಂಟೆನ್ಸಿವ್ ಮತ್ತು ಸ್ವಾಭಾವಿಕವಾಗಿ ವಿಭಜಿಸಿ-ಮತ್ತು-ಗೆಲ್ಲಿ ತಂತ್ರಕ್ಕೆ ಹೊಂದಿಕೊಳ್ಳುವ ಕಾರ್ಯಗಳಿಗೆ.

ಪ್ಯಾರಲಲಿಸಂನ ಅವಶ್ಯಕತೆಯನ್ನು ಅರ್ಥಮಾಡಿಕೊಳ್ಳುವುದು

ಫೋರ್ಕ್-ಜಾಯಿನ್ ಫ್ರೇಮ್‌ವರ್ಕ್‌ನ ನಿರ್ದಿಷ್ಟತೆಗಳಿಗೆ ಧುಮುಕುವ ಮೊದಲು, ಪ್ಯಾರಲಲ್ ಪ್ರೊಸೆಸಿಂಗ್ ಏಕೆ ಅತ್ಯಗತ್ಯ ಎಂಬುದನ್ನು ಗ್ರಹಿಸುವುದು ಬಹಳ ಮುಖ್ಯ. ಸಾಂಪ್ರದಾಯಿಕವಾಗಿ, ಅಪ್ಲಿಕೇಶನ್‌ಗಳು ಕಾರ್ಯಗಳನ್ನು ಅನುಕ್ರಮವಾಗಿ, ಒಂದರ ನಂತರ ಒಂದರಂತೆ ಕಾರ್ಯಗತಗೊಳಿಸುತ್ತಿದ್ದವು. ಈ ವಿಧಾನವು ಸರಳವಾಗಿದ್ದರೂ, ಆಧುನಿಕ ಗಣಕೀಕೃತ ಬೇಡಿಕೆಗಳನ್ನು ನಿಭಾಯಿಸುವಾಗ ಇದು ಒಂದು ಅಡಚಣೆಯಾಗುತ್ತದೆ. ಲಕ್ಷಾಂತರ ವಹಿವಾಟುಗಳನ್ನು ಪ್ರಕ್ರಿಯೆಗೊಳಿಸಬೇಕಾದ, ವಿವಿಧ ಪ್ರದೇಶಗಳಿಂದ ಬಳಕೆದಾರರ ವರ್ತನೆಯ ಡೇಟಾವನ್ನು ವಿಶ್ಲೇಷಿಸಬೇಕಾದ, ಅಥವಾ ನೈಜ-ಸಮಯದಲ್ಲಿ ಸಂಕೀರ್ಣ ದೃಶ್ಯ ಇಂಟರ್ಫೇಸ್‌ಗಳನ್ನು ನಿರೂಪಿಸಬೇಕಾದ ಜಾಗತಿಕ ಇ-ಕಾಮರ್ಸ್ ಪ್ಲಾಟ್‌ಫಾರ್ಮ್ ಅನ್ನು ಪರಿಗಣಿಸಿ. ಏಕ-ಥ್ರೆಡ್‌ನ ಕಾರ್ಯಗತಗೊಳಿಸುವಿಕೆಯು ನಿಷೇಧಾತ್ಮಕವಾಗಿ ನಿಧಾನವಾಗಿರುತ್ತದೆ, ಇದು ಕಳಪೆ ಬಳಕೆದಾರ ಅನುಭವಗಳಿಗೆ ಮತ್ತು ತಪ್ಪಿದ ವ್ಯಾಪಾರ ಅವಕಾಶಗಳಿಗೆ ಕಾರಣವಾಗುತ್ತದೆ.

ಮೊಬೈಲ್ ಫೋನ್‌ಗಳಿಂದ ಹಿಡಿದು ಬೃಹತ್ ಸರ್ವರ್ ಕ್ಲಸ್ಟರ್‌ಗಳವರೆಗೆ ಹೆಚ್ಚಿನ ಕಂಪ್ಯೂಟಿಂಗ್ ಸಾಧನಗಳಲ್ಲಿ ಮಲ್ಟಿ-ಕೋರ್ ಪ್ರೊಸೆಸರ್‌ಗಳು ಈಗ ಪ್ರಮಾಣಿತವಾಗಿವೆ. ಪ್ಯಾರಲಲಿಸಂ ನಮಗೆ ಈ ಬಹು ಕೋರ್‌ಗಳ ಶಕ್ತಿಯನ್ನು ಬಳಸಿಕೊಳ್ಳಲು ಅನುವು ಮಾಡಿಕೊಡುತ್ತದೆ, ಇದರಿಂದ ಅಪ್ಲಿಕೇಶನ್‌ಗಳು ಒಂದೇ ಸಮಯದಲ್ಲಿ ಹೆಚ್ಚು ಕೆಲಸ ಮಾಡಲು ಸಾಧ್ಯವಾಗುತ್ತದೆ. ಇದು ಈ ಕೆಳಗಿನವುಗಳಿಗೆ ಕಾರಣವಾಗುತ್ತದೆ:

ವಿಭಜಿಸಿ-ಮತ್ತು-ಗೆಲ್ಲಿ (Divide-and-Conquer) ಮಾದರಿ

ಫೋರ್ಕ್-ಜಾಯಿನ್ ಫ್ರೇಮ್‌ವರ್ಕ್ ಸುಸ್ಥಾಪಿತ ವಿಭಜಿಸಿ-ಮತ್ತು-ಗೆಲ್ಲಿ ಅಲ್ಗಾರಿದಮಿಕ್ ಮಾದರಿಯ ಮೇಲೆ ನಿರ್ಮಿತವಾಗಿದೆ. ಈ ವಿಧಾನವು ಇವುಗಳನ್ನು ಒಳಗೊಂಡಿರುತ್ತದೆ:

  1. ವಿಭಜಿಸಿ (Divide): ಒಂದು ಸಂಕೀರ್ಣ ಸಮಸ್ಯೆಯನ್ನು ಸಣ್ಣ, ಸ್ವತಂತ್ರ ಉಪ-ಸಮಸ್ಯೆಗಳಾಗಿ ವಿಭಜಿಸುವುದು.
  2. ಗೆಲ್ಲಿ (Conquer): ಈ ಉಪ-ಸಮಸ್ಯೆಗಳನ್ನು ರಿಕರ್ಸಿವ್ ಆಗಿ ಪರಿಹರಿಸುವುದು. ಒಂದು ವೇಳೆ ಉಪ-ಸಮಸ್ಯೆ ಸಾಕಷ್ಟು ಚಿಕ್ಕದಾಗಿದ್ದರೆ, ಅದನ್ನು ನೇರವಾಗಿ ಪರಿಹರಿಸಲಾಗುತ್ತದೆ. ಇಲ್ಲದಿದ್ದರೆ, ಅದನ್ನು ಮತ್ತಷ್ಟು ವಿಭಜಿಸಲಾಗುತ್ತದೆ.
  3. ಸಂಯೋಜಿಸಿ (Combine): ಮೂಲ ಸಮಸ್ಯೆಯ ಪರಿಹಾರವನ್ನು ರೂಪಿಸಲು ಉಪ-ಸಮಸ್ಯೆಗಳ ಪರಿಹಾರಗಳನ್ನು ವಿಲೀನಗೊಳಿಸುವುದು.

ಈ ರಿಕರ್ಸಿವ್ ಸ್ವಭಾವವು ಫೋರ್ಕ್-ಜಾಯಿನ್ ಫ್ರೇಮ್‌ವರ್ಕ್ ಅನ್ನು ಈ ಕೆಳಗಿನಂತಹ ಕಾರ್ಯಗಳಿಗೆ ವಿಶೇಷವಾಗಿ ಸೂಕ್ತವಾಗಿಸುತ್ತದೆ:

ಜಾವಾದಲ್ಲಿ ಫೋರ್ಕ್-ಜಾಯಿನ್ ಫ್ರೇಮ್‌ವರ್ಕ್‌ನ ಪರಿಚಯ

ಜಾವಾ 7 ರಲ್ಲಿ ಪರಿಚಯಿಸಲಾದ ಜಾವಾದ ಫೋರ್ಕ್-ಜಾಯಿನ್ ಫ್ರೇಮ್‌ವರ್ಕ್, ವಿಭಜಿಸಿ-ಮತ್ತು-ಗೆಲ್ಲಿ ತಂತ್ರವನ್ನು ಆಧರಿಸಿ ಪ್ಯಾರಲಲ್ ಅಲ್ಗಾರಿದಮ್‌ಗಳನ್ನು ಕಾರ್ಯಗತಗೊಳಿಸಲು ಒಂದು ರಚನಾತ್ಮಕ ಮಾರ್ಗವನ್ನು ಒದಗಿಸುತ್ತದೆ. ಇದು ಎರಡು ಮುಖ್ಯ ಅಬ್ಸ್‌ಟ್ರಾಕ್ಟ್ ಕ್ಲಾಸ್‌ಗಳನ್ನು ಒಳಗೊಂಡಿದೆ:

ಈ ಕ್ಲಾಸ್‌ಗಳನ್ನು ForkJoinPool ಎಂಬ ವಿಶೇಷ ರೀತಿಯ ExecutorService ಜೊತೆಗೆ ಬಳಸಲು ವಿನ್ಯಾಸಗೊಳಿಸಲಾಗಿದೆ. ForkJoinPool ಫೋರ್ಕ್-ಜಾಯಿನ್ ಕಾರ್ಯಗಳಿಗಾಗಿ ಆಪ್ಟಿಮೈಜ್ ಮಾಡಲಾಗಿದೆ ಮತ್ತು ವರ್ಕ್-ಸ್ಟೀಲಿಂಗ್ ಎಂಬ ತಂತ್ರವನ್ನು ಬಳಸುತ್ತದೆ, ಇದು ಅದರ ದಕ್ಷತೆಗೆ ಪ್ರಮುಖವಾಗಿದೆ.

ಫ್ರೇಮ್‌ವರ್ಕ್‌ನ ಪ್ರಮುಖ ಅಂಶಗಳು

ಫೋರ್ಕ್-ಜಾಯಿನ್ ಫ್ರೇಮ್‌ವರ್ಕ್‌ನೊಂದಿಗೆ ಕೆಲಸ ಮಾಡುವಾಗ ನೀವು ಎದುರಿಸುವ ಪ್ರಮುಖ ಅಂಶಗಳನ್ನು ವಿಭಜಿಸೋಣ:

1. ForkJoinPool

ForkJoinPool ಈ ಫ್ರೇಮ್‌ವರ್ಕ್‌ನ ಹೃದಯವಾಗಿದೆ. ಇದು ಕಾರ್ಯಗಳನ್ನು ಕಾರ್ಯಗತಗೊಳಿಸುವ ವರ್ಕರ್ ಥ್ರೆಡ್‌ಗಳ ಪೂಲ್ ಅನ್ನು ನಿರ್ವಹಿಸುತ್ತದೆ. ಸಾಂಪ್ರದಾಯಿಕ ಥ್ರೆಡ್ ಪೂಲ್‌ಗಳಿಗಿಂತ ಭಿನ್ನವಾಗಿ, ForkJoinPool ಅನ್ನು ವಿಶೇಷವಾಗಿ ಫೋರ್ಕ್-ಜಾಯಿನ್ ಮಾದರಿಗಾಗಿ ವಿನ್ಯಾಸಗೊಳಿಸಲಾಗಿದೆ. ಇದರ ಮುಖ್ಯ ಲಕ್ಷಣಗಳು:

ನೀವು ಈ ರೀತಿ ForkJoinPool ಅನ್ನು ರಚಿಸಬಹುದು:

// Using the common pool (recommended for most cases)
ForkJoinPool pool = ForkJoinPool.commonPool();

// Or creating a custom pool
// ForkJoinPool customPool = new ForkJoinPool(Runtime.getRuntime().availableProcessors());

commonPool() ಒಂದು ಸ್ಟ್ಯಾಟಿಕ್, ಹಂಚಿಕೆಯ ಪೂಲ್ ಆಗಿದ್ದು, ಇದನ್ನು ನೀವು ಸ್ಪಷ್ಟವಾಗಿ ರಚಿಸದೆ ಮತ್ತು ನಿರ್ವಹಿಸದೆ ಬಳಸಬಹುದು. ಇದು ಸಾಮಾನ್ಯವಾಗಿ ಲಭ್ಯವಿರುವ ಪ್ರೊಸೆಸರ್‌ಗಳ ಸಂಖ್ಯೆಯನ್ನು ಆಧರಿಸಿ ಸೂಕ್ತ ಸಂಖ್ಯೆಯ ಥ್ರೆಡ್‌ಗಳೊಂದಿಗೆ ಪೂರ್ವ-ಕಾನ್ಫಿಗರ್ ಮಾಡಲಾಗಿರುತ್ತದೆ.

2. RecursiveTask<V>

RecursiveTask<V> ಒಂದು ಅಬ್ಸ್‌ಟ್ರಾಕ್ಟ್ ಕ್ಲಾಸ್ ಆಗಿದ್ದು, ಇದು V ಮಾದರಿಯ ಫಲಿತಾಂಶವನ್ನು ಗಣಿಸುವ ಕಾರ್ಯವನ್ನು ಪ್ರತಿನಿಧಿಸುತ್ತದೆ. ಇದನ್ನು ಬಳಸಲು, ನೀವು ಹೀಗೆ ಮಾಡಬೇಕಾಗುತ್ತದೆ:

compute() ವಿಧಾನದೊಳಗೆ, ನೀವು ಸಾಮಾನ್ಯವಾಗಿ ಹೀಗೆ ಮಾಡುತ್ತೀರಿ:

ಉದಾಹರಣೆ: ಒಂದು ಅರೇಯಲ್ಲಿನ ಸಂಖ್ಯೆಗಳ ಮೊತ್ತವನ್ನು ಲೆಕ್ಕಾಚಾರ ಮಾಡುವುದು

ಒಂದು ದೊಡ್ಡ ಅರೇಯಲ್ಲಿನ ಅಂಶಗಳ ಮೊತ್ತವನ್ನು ಕಂಡುಹಿಡಿಯುವ ಒಂದು ಶ್ರೇಷ್ಠ ಉದಾಹರಣೆಯೊಂದಿಗೆ ವಿವರಿಸೋಣ.

import java.util.concurrent.RecursiveTask;

public class SumArrayTask extends RecursiveTask<Long> {

    private static final int THRESHOLD = 1000; // Threshold for splitting
    private final int[] array;
    private final int start;
    private final int end;

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

    @Override
    protected Long compute() {
        int length = end - start;

        // Base case: If the sub-array is small enough, sum it directly
        if (length <= THRESHOLD) {
            return sequentialSum(array, start, end);
        }

        // Recursive case: Split the task into two sub-tasks
        int mid = start + length / 2;

        SumArrayTask leftTask = new SumArrayTask(array, start, mid);
        SumArrayTask rightTask = new SumArrayTask(array, mid, end);

        // Fork the left task (schedule it for execution)
        leftTask.fork();

        // Compute the right task directly (or fork it as well)
        // Here, we compute the right task directly to keep one thread busy
        Long rightResult = rightTask.compute();

        // Join the left task (wait for its result)
        Long leftResult = leftTask.join();

        // Combine the results
        return leftResult + rightResult;
    }

    private Long sequentialSum(int[] array, int start, int end) {
        Long sum = 0L;
        for (int i = start; i < end; i++) {
            sum += array[i];
        }
        return sum;
    }

    public static void main(String[] args) {
        int[] data = new int[1000000]; // Example large array
        for (int i = 0; i < data.length; i++) {
            data[i] = i % 100;
        }

        ForkJoinPool pool = ForkJoinPool.commonPool();
        SumArrayTask task = new SumArrayTask(data, 0, data.length);

        System.out.println("Calculating sum...");
        long startTime = System.nanoTime();
        Long result = pool.invoke(task);
        long endTime = System.nanoTime();

        System.out.println("Sum: " + result);
        System.out.println("Time taken: " + (endTime - startTime) / 1_000_000 + " ms");

        // For comparison, a sequential sum
        // long sequentialResult = 0;
        // for (int val : data) {
        //     sequentialResult += val;
        // }
        // System.out.println("Sequential Sum: " + sequentialResult);
    }
}

ಈ ಉದಾಹರಣೆಯಲ್ಲಿ:

3. RecursiveAction

RecursiveAction RecursiveTask ಗೆ ಹೋಲುತ್ತದೆ ಆದರೆ ಇದು ಯಾವುದೇ ಮೌಲ್ಯವನ್ನು ಹಿಂತಿರುಗಿಸದ ಕಾರ್ಯಗಳಿಗಾಗಿ ಬಳಸಲ್ಪಡುತ್ತದೆ. ಮೂಲ ತರ್ಕವು ಒಂದೇ ಆಗಿರುತ್ತದೆ: ಕಾರ್ಯವು ದೊಡ್ಡದಾಗಿದ್ದರೆ ಅದನ್ನು ವಿಭಜಿಸಿ, ಉಪ-ಕಾರ್ಯಗಳನ್ನು ಫೋರ್ಕ್ ಮಾಡಿ, ಮತ್ತು ನಂತರ ಅವುಗಳ ಪೂರ್ಣಗೊಳ್ಳುವಿಕೆಯು ಮುಂದುವರಿಯುವ ಮೊದಲು ಅಗತ್ಯವಿದ್ದರೆ ಅವುಗಳನ್ನು ಜಾಯಿನ್ ಮಾಡಿ.

ಒಂದು RecursiveAction ಅನ್ನು ಕಾರ್ಯಗತಗೊಳಿಸಲು, ನೀವು ಹೀಗೆ ಮಾಡುತ್ತೀರಿ:

compute() ಒಳಗೆ, ನೀವು ಉಪ-ಕಾರ್ಯಗಳನ್ನು ಶೆಡ್ಯೂಲ್ ಮಾಡಲು fork() ಮತ್ತು ಅವುಗಳ ಪೂರ್ಣಗೊಳ್ಳುವಿಕೆಗಾಗಿ ಕಾಯಲು join() ಅನ್ನು ಬಳಸುತ್ತೀರಿ. ಯಾವುದೇ ಹಿಂತಿರುಗುವ ಮೌಲ್ಯವಿಲ್ಲದ ಕಾರಣ, ನೀವು ಸಾಮಾನ್ಯವಾಗಿ ಫಲಿತಾಂಶಗಳನ್ನು "ಸಂಯೋಜಿಸಬೇಕಾಗಿಲ್ಲ", ಆದರೆ ಆಕ್ಷನ್ ಸ್ವತಃ ಪೂರ್ಣಗೊಳ್ಳುವ ಮೊದಲು ಎಲ್ಲಾ ಅವಲಂಬಿತ ಉಪ-ಕಾರ್ಯಗಳು ಮುಗಿದಿವೆ ಎಂದು ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಬೇಕಾಗಬಹುದು.

ಉದಾಹರಣೆ: ಪ್ಯಾರಲಲ್ ಅರೇ ಎಲಿಮೆಂಟ್ ಟ್ರಾನ್ಸ್‌ಫಾರ್ಮೇಶನ್

ಒಂದು ಅರೇಯ ಪ್ರತಿಯೊಂದು ಅಂಶವನ್ನು ಪ್ಯಾರಲಲ್ ಆಗಿ ಪರಿವರ್ತಿಸುವುದನ್ನು ಕಲ್ಪಿಸಿಕೊಳ್ಳೋಣ, ಉದಾಹರಣೆಗೆ, ಪ್ರತಿ ಸಂಖ್ಯೆಯನ್ನು ವರ್ಗೀಕರಿಸುವುದು.

import java.util.concurrent.RecursiveAction;
import java.util.concurrent.ForkJoinPool;

public class SquareArrayAction extends RecursiveAction {

    private static final int THRESHOLD = 1000;
    private final int[] array;
    private final int start;
    private final int end;

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

    @Override
    protected void compute() {
        int length = end - start;

        // Base case: If the sub-array is small enough, transform it sequentially
        if (length <= THRESHOLD) {
            sequentialSquare(array, start, end);
            return; // No result to return
        }

        // Recursive case: Split the task
        int mid = start + length / 2;

        SquareArrayAction leftAction = new SquareArrayAction(array, start, mid);
        SquareArrayAction rightAction = new SquareArrayAction(array, mid, end);

        // Fork both sub-actions
        // Using invokeAll is often more efficient for multiple forked tasks
        invokeAll(leftAction, rightAction);

        // No explicit join needed after invokeAll if we don't depend on intermediate results
        // If you were to fork individually and then join:
        // leftAction.fork();
        // rightAction.fork();
        // leftAction.join();
        // rightAction.join();
    }

    private void sequentialSquare(int[] array, int start, int end) {
        for (int i = start; i < end; i++) {
            array[i] = array[i] * array[i];
        }
    }

    public static void main(String[] args) {
        int[] data = new int[1000000];
        for (int i = 0; i < data.length; i++) {
            data[i] = (i % 50) + 1; // Values from 1 to 50
        }

        ForkJoinPool pool = ForkJoinPool.commonPool();
        SquareArrayAction action = new SquareArrayAction(data, 0, data.length);

        System.out.println("Squaring array elements...");
        long startTime = System.nanoTime();
        pool.invoke(action); // invoke() for actions also waits for completion
        long endTime = System.nanoTime();

        System.out.println("Array transformation complete.");
        System.out.println("Time taken: " + (endTime - startTime) / 1_000_000 + " ms");

        // Optionally print first few elements to verify
        // System.out.println("First 10 elements after squaring:");
        // for (int i = 0; i < 10; i++) {
        //     System.out.print(data[i] + " ");
        // }
        // System.out.println();
    }
}

ಇಲ್ಲಿನ ಪ್ರಮುಖ ಅಂಶಗಳು:

ಸುಧಾರಿತ ಫೋರ್ಕ್-ಜಾಯಿನ್ ಪರಿಕಲ್ಪನೆಗಳು ಮತ್ತು ಉತ್ತಮ ಅಭ್ಯಾಸಗಳು

ಫೋರ್ಕ್-ಜಾಯಿನ್ ಫ್ರೇಮ್‌ವರ್ಕ್ ಶಕ್ತಿಯುತವಾಗಿದ್ದರೂ, ಅದರಲ್ಲಿ ಪ್ರಾವೀಣ್ಯತೆ ಪಡೆಯಲು ಕೆಲವು ಸೂಕ್ಷ್ಮ ವ್ಯತ್ಯಾಸಗಳನ್ನು ಅರ್ಥಮಾಡಿಕೊಳ್ಳುವುದು ಅವಶ್ಯಕ:

1. ಸರಿಯಾದ ಥ್ರೆಶೋಲ್ಡ್ ಅನ್ನು ಆರಿಸುವುದು

THRESHOLD ನಿರ್ಣಾಯಕವಾಗಿದೆ. ಅದು ತುಂಬಾ ಕಡಿಮೆಯಿದ್ದರೆ, ನೀವು ಅನೇಕ ಸಣ್ಣ ಕಾರ್ಯಗಳನ್ನು ರಚಿಸುವ ಮತ್ತು ನಿರ್ವಹಿಸುವ ಹೆಚ್ಚಿನ ಓವರ್‌ಹೆಡ್ ಅನ್ನು ಅನುಭವಿಸುವಿರಿ. ಅದು ತುಂಬಾ ಹೆಚ್ಚಿದ್ದರೆ, ನೀವು ಬಹು ಕೋರ್‌ಗಳನ್ನು ಪರಿಣಾಮಕಾರಿಯಾಗಿ ಬಳಸಿಕೊಳ್ಳುವುದಿಲ್ಲ, ಮತ್ತು ಪ್ಯಾರಲಲಿಸಂನ ಪ್ರಯೋಜನಗಳು ಕಡಿಮೆಯಾಗುತ್ತವೆ. ಯಾವುದೇ ಸಾರ್ವತ್ರಿಕ ಮ್ಯಾಜಿಕ್ ಸಂಖ್ಯೆ ಇಲ್ಲ; ಸೂಕ್ತವಾದ ಥ್ರೆಶೋಲ್ಡ್ ಸಾಮಾನ್ಯವಾಗಿ ನಿರ್ದಿಷ್ಟ ಕಾರ್ಯ, ಡೇಟಾ ಗಾತ್ರ ಮತ್ತು ಆಧಾರವಾಗಿರುವ ಹಾರ್ಡ್‌ವೇರ್ ಅನ್ನು ಅವಲಂಬಿಸಿರುತ್ತದೆ. ಪ್ರಯೋಗ ಮಾಡುವುದು ಮುಖ್ಯ. ಅನುಕ್ರಮ ಕಾರ್ಯಗತಗೊಳಿಸುವಿಕೆಯು ಕೆಲವು ಮಿಲಿಸೆಕೆಂಡ್‌ಗಳನ್ನು ತೆಗೆದುಕೊಳ್ಳುವಂತಹ ಮೌಲ್ಯವು ಸಾಮಾನ್ಯವಾಗಿ ಉತ್ತಮ ಆರಂಭಿಕ ಹಂತವಾಗಿದೆ.

2. ಅತಿಯಾದ ಫೋರ್ಕಿಂಗ್ ಮತ್ತು ಜಾಯಿನಿಂಗ್ ಅನ್ನು ತಪ್ಪಿಸುವುದು

ಆಗಾಗ್ಗೆ ಮತ್ತು ಅನಗತ್ಯವಾದ ಫೋರ್ಕಿಂಗ್ ಮತ್ತು ಜಾಯಿನಿಂಗ್ ಕಾರ್ಯಕ್ಷಮತೆಯ ಅವನತಿಗೆ ಕಾರಣವಾಗಬಹುದು. ಪ್ರತಿ fork() ಕರೆಯು ಪೂಲ್‌ಗೆ ಒಂದು ಕಾರ್ಯವನ್ನು ಸೇರಿಸುತ್ತದೆ, ಮತ್ತು ಪ್ರತಿ join() ಸಂಭಾವ್ಯವಾಗಿ ಒಂದು ಥ್ರೆಡ್ ಅನ್ನು ಬ್ಲಾಕ್ ಮಾಡಬಹುದು. ಯಾವಾಗ ಫೋರ್ಕ್ ಮಾಡಬೇಕು ಮತ್ತು ಯಾವಾಗ ನೇರವಾಗಿ ಗಣಿಸಬೇಕು ಎಂದು ಆಯಕಟ್ಟಿನ ರೀತಿಯಲ್ಲಿ ನಿರ್ಧರಿಸಿ. SumArrayTask ಉದಾಹರಣೆಯಲ್ಲಿ ನೋಡಿದಂತೆ, ಒಂದು ಶಾಖೆಯನ್ನು ನೇರವಾಗಿ ಗಣಿಸುವಾಗ ಇನ್ನೊಂದನ್ನು ಫೋರ್ಕ್ ಮಾಡುವುದು ಥ್ರೆಡ್‌ಗಳನ್ನು ಕಾರ್ಯನಿರತವಾಗಿಡಲು ಸಹಾಯ ಮಾಡುತ್ತದೆ.

3. invokeAll ಅನ್ನು ಬಳಸುವುದು

ನೀವು ಮುಂದುವರಿಯುವ ಮೊದಲು ಪೂರ್ಣಗೊಳಿಸಬೇಕಾದ ಅನೇಕ ಸ್ವತಂತ್ರ ಉಪ-ಕಾರ್ಯಗಳನ್ನು ಹೊಂದಿರುವಾಗ, ಪ್ರತಿ ಕಾರ್ಯವನ್ನು ಕೈಯಾರೆ ಫೋರ್ಕ್ ಮತ್ತು ಜಾಯಿನ್ ಮಾಡುವುದಕ್ಕಿಂತ invokeAll ಅನ್ನು ಬಳಸುವುದು ಸಾಮಾನ್ಯವಾಗಿ ಉತ್ತಮ. ಇದು ಉತ್ತಮ ಥ್ರೆಡ್ ಬಳಕೆ ಮತ್ತು ಲೋಡ್ ಬ್ಯಾಲೆನ್ಸಿಂಗ್‌ಗೆ ಕಾರಣವಾಗುತ್ತದೆ.

4. ವಿನಾಯಿತಿಗಳನ್ನು (Exceptions) ನಿರ್ವಹಿಸುವುದು

compute() ವಿಧಾನದೊಳಗೆ ಎಸೆಯಲಾದ ವಿನಾಯಿತಿಗಳನ್ನು ನೀವು ಕಾರ್ಯವನ್ನು join() ಅಥವಾ invoke() ಮಾಡಿದಾಗ RuntimeException (ಸಾಮಾನ್ಯವಾಗಿ CompletionException) ನಲ್ಲಿ ಸುತ್ತಿಡಲಾಗುತ್ತದೆ. ನೀವು ಈ ವಿನಾಯಿತಿಗಳನ್ನು ಅನ್‌ವ್ರ್ಯಾಪ್ ಮಾಡಿ ಸೂಕ್ತವಾಗಿ ನಿರ್ವಹಿಸಬೇಕಾಗುತ್ತದೆ.

try {
    Long result = pool.invoke(task);
} catch (CompletionException e) {
    // Handle the exception thrown by the task
    Throwable cause = e.getCause();
    if (cause instanceof IllegalArgumentException) {
        // Handle specific exceptions
    } else {
        // Handle other exceptions
    }
}

5. ಕಾಮನ್ ಪೂಲ್ ಅನ್ನು ಅರ್ಥಮಾಡಿಕೊಳ್ಳುವುದು

ಹೆಚ್ಚಿನ ಅಪ್ಲಿಕೇಶನ್‌ಗಳಿಗೆ, ForkJoinPool.commonPool() ಅನ್ನು ಬಳಸುವುದು ಶಿಫಾರಸು ಮಾಡಲಾದ ವಿಧಾನವಾಗಿದೆ. ಇದು ಬಹು ಪೂಲ್‌ಗಳನ್ನು ನಿರ್ವಹಿಸುವ ಓವರ್‌ಹೆಡ್ ಅನ್ನು ತಪ್ಪಿಸುತ್ತದೆ ಮತ್ತು ನಿಮ್ಮ ಅಪ್ಲಿಕೇಶನ್‌ನ ವಿವಿಧ ಭಾಗಗಳಿಂದ ಕಾರ್ಯಗಳು ಒಂದೇ ಥ್ರೆಡ್‌ಗಳ ಪೂಲ್ ಅನ್ನು ಹಂಚಿಕೊಳ್ಳಲು ಅನುವು ಮಾಡಿಕೊಡುತ್ತದೆ. ಆದಾಗ್ಯೂ, ನಿಮ್ಮ ಅಪ್ಲಿಕೇಶನ್‌ನ ಇತರ ಭಾಗಗಳು ಸಹ ಕಾಮನ್ ಪೂಲ್ ಅನ್ನು ಬಳಸುತ್ತಿರಬಹುದು ಎಂಬುದನ್ನು ನೆನಪಿನಲ್ಲಿಡಿ, ಇದು ಎಚ್ಚರಿಕೆಯಿಂದ ನಿರ್ವಹಿಸದಿದ್ದರೆ ಸಂಭಾವ್ಯವಾಗಿ ಸ್ಪರ್ಧೆಗೆ ಕಾರಣವಾಗಬಹುದು.

6. ಫೋರ್ಕ್-ಜಾಯಿನ್ ಅನ್ನು ಯಾವಾಗ ಬಳಸಬಾರದು

ಫೋರ್ಕ್-ಜಾಯಿನ್ ಫ್ರೇಮ್‌ವರ್ಕ್ ಅನ್ನು ಕಂಪ್ಯೂಟ್-ಬೌಂಡ್ ಕಾರ್ಯಗಳಿಗಾಗಿ ಆಪ್ಟಿಮೈಜ್ ಮಾಡಲಾಗಿದೆ, ಇವುಗಳನ್ನು ಪರಿಣಾಮಕಾರಿಯಾಗಿ ಸಣ್ಣ, ರಿಕರ್ಸಿವ್ ತುಣುಕುಗಳಾಗಿ ವಿಭಜಿಸಬಹುದು. ಇದು ಸಾಮಾನ್ಯವಾಗಿ ಈ ಕೆಳಗಿನವುಗಳಿಗೆ ಸೂಕ್ತವಲ್ಲ:

ಜಾಗತಿಕ ಪರಿಗಣನೆಗಳು ಮತ್ತು ಬಳಕೆಯ ಪ್ರಕರಣಗಳು

ಮಲ್ಟಿ-ಕೋರ್ ಪ್ರೊಸೆಸರ್‌ಗಳನ್ನು ಸಮರ್ಥವಾಗಿ ಬಳಸಿಕೊಳ್ಳುವ ಫೋರ್ಕ್-ಜಾಯಿನ್ ಫ್ರೇಮ್‌ವರ್ಕ್‌ನ ಸಾಮರ್ಥ್ಯವು ಜಾಗತಿಕ ಅಪ್ಲಿಕೇಶನ್‌ಗಳಿಗೆ ಅಮೂಲ್ಯವಾಗಿದೆ, ಏಕೆಂದರೆ ಅವುಗಳು ಸಾಮಾನ್ಯವಾಗಿ ಇವುಗಳನ್ನು ನಿಭಾಯಿಸುತ್ತವೆ:

ಜಾಗತಿಕ ಪ್ರೇಕ್ಷಕರಿಗಾಗಿ ಅಭಿವೃದ್ಧಿಪಡಿಸುವಾಗ, ಕಾರ್ಯಕ್ಷಮತೆ ಮತ್ತು ಸ್ಪಂದನಾಶೀಲತೆ ನಿರ್ಣಾಯಕವಾಗಿರುತ್ತದೆ. ಫೋರ್ಕ್-ಜಾಯಿನ್ ಫ್ರೇಮ್‌ವರ್ಕ್ ನಿಮ್ಮ ಜಾವಾ ಅಪ್ಲಿಕೇಶನ್‌ಗಳು ಪರಿಣಾಮಕಾರಿಯಾಗಿ ಸ್ಕೇಲ್ ಆಗುವುದನ್ನು ಮತ್ತು ನಿಮ್ಮ ಬಳಕೆದಾರರ ಭೌಗೋಳಿಕ ಹಂಚಿಕೆ ಅಥವಾ ನಿಮ್ಮ ಸಿಸ್ಟಮ್‌ಗಳ ಮೇಲೆ ಹೇರಲಾದ ಗಣಕೀಕೃತ ಬೇಡಿಕೆಗಳನ್ನು ಲೆಕ್ಕಿಸದೆ ಸುಗಮ ಅನುಭವವನ್ನು ನೀಡುವುದನ್ನು ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಲು ಒಂದು ದೃಢವಾದ ಯಾಂತ್ರಿಕತೆಯನ್ನು ಒದಗಿಸುತ್ತದೆ.

ತೀರ್ಮಾನ

ಫೋರ್ಕ್-ಜಾಯಿನ್ ಫ್ರೇಮ್‌ವರ್ಕ್ ಆಧುನಿಕ ಜಾವಾ ಡೆವಲಪರ್‌ಗಳ ಶಸ್ತ್ರಾಗಾರದಲ್ಲಿ ಗಣಕೀಕೃತವಾಗಿ ತೀವ್ರವಾದ ಕಾರ್ಯಗಳನ್ನು ಪ್ಯಾರಲಲ್ ಆಗಿ ನಿಭಾಯಿಸಲು ಒಂದು ಅನಿವಾರ್ಯ ಸಾಧನವಾಗಿದೆ. ವಿಭಜಿಸಿ-ಮತ್ತು-ಗೆಲ್ಲಿ ತಂತ್ರವನ್ನು ಅಳವಡಿಸಿಕೊಳ್ಳುವ ಮೂಲಕ ಮತ್ತು ForkJoinPool ನೊಳಗೆ ವರ್ಕ್-ಸ್ಟೀಲಿಂಗ್ ಶಕ್ತಿಯನ್ನು ಬಳಸಿಕೊಳ್ಳುವ ಮೂಲಕ, ನಿಮ್ಮ ಅಪ್ಲಿಕೇಶನ್‌ಗಳ ಕಾರ್ಯಕ್ಷಮತೆ ಮತ್ತು ಸ್ಕೇಲೆಬಿಲಿಟಿಯನ್ನು ನೀವು ಗಮನಾರ್ಹವಾಗಿ ಹೆಚ್ಚಿಸಬಹುದು. RecursiveTask ಮತ್ತು RecursiveAction ಅನ್ನು ಸರಿಯಾಗಿ ಹೇಗೆ ವ್ಯಾಖ್ಯಾನಿಸುವುದು, ಸೂಕ್ತವಾದ ಥ್ರೆಶೋಲ್ಡ್‌ಗಳನ್ನು ಆಯ್ಕೆ ಮಾಡುವುದು, ಮತ್ತು ಕಾರ್ಯ ಅವಲಂಬನೆಗಳನ್ನು ನಿರ್ವಹಿಸುವುದನ್ನು ಅರ್ಥಮಾಡಿಕೊಳ್ಳುವುದು ಮಲ್ಟಿ-ಕೋರ್ ಪ್ರೊಸೆಸರ್‌ಗಳ ಸಂಪೂರ್ಣ ಸಾಮರ್ಥ್ಯವನ್ನು ಅನ್ಲಾಕ್ ಮಾಡಲು ನಿಮಗೆ ಅನುವು ಮಾಡಿಕೊಡುತ್ತದೆ. ಜಾಗತಿಕ ಅಪ್ಲಿಕೇಶನ್‌ಗಳು ಸಂಕೀರ್ಣತೆ ಮತ್ತು ಡೇಟಾ ಪ್ರಮಾಣದಲ್ಲಿ ಬೆಳೆಯುತ್ತಲೇ ಇರುವುದರಿಂದ, ವಿಶ್ವಾದ್ಯಂತದ ಬಳಕೆದಾರರ ಬೇಡಿಕೆಗಳನ್ನು ಪೂರೈಸುವ ದಕ್ಷ, ಸ್ಪಂದನಾಶೀಲ, ಮತ್ತು ಉನ್ನತ-ಕಾರ್ಯಕ್ಷಮತೆಯ ಸಾಫ್ಟ್‌ವೇರ್ ಪರಿಹಾರಗಳನ್ನು ನಿರ್ಮಿಸಲು ಫೋರ್ಕ್-ಜಾಯಿನ್ ಫ್ರೇಮ್‌ವರ್ಕ್‌ನಲ್ಲಿ ಪ್ರಾವೀಣ್ಯತೆ ಪಡೆಯುವುದು ಅತ್ಯಗತ್ಯ.

ನಿಮ್ಮ ಅಪ್ಲಿಕೇಶನ್‌ನಲ್ಲಿ ರಿಕರ್ಸಿವ್ ಆಗಿ ವಿಭಜಿಸಬಹುದಾದ ಕಂಪ್ಯೂಟ್-ಬೌಂಡ್ ಕಾರ್ಯಗಳನ್ನು ಗುರುತಿಸುವ ಮೂಲಕ ಪ್ರಾರಂಭಿಸಿ. ಫ್ರೇಮ್‌ವರ್ಕ್‌ನೊಂದಿಗೆ ಪ್ರಯೋಗ ಮಾಡಿ, ಕಾರ್ಯಕ್ಷಮತೆಯ ಲಾಭಗಳನ್ನು ಅಳೆಯಿರಿ, ಮತ್ತು ಅತ್ಯುತ್ತಮ ಫಲಿತಾಂಶಗಳನ್ನು ಸಾಧಿಸಲು ನಿಮ್ಮ ಅನುಷ್ಠಾನಗಳನ್ನು ಉತ್ತಮಗೊಳಿಸಿ. ದಕ್ಷ ಪ್ಯಾರಲಲ್ ಕಾರ್ಯಗತಗೊಳಿಸುವಿಕೆಯ ಪ್ರಯಾಣವು ನಿರಂತರವಾಗಿರುತ್ತದೆ, ಮತ್ತು ಫೋರ್ಕ್-ಜಾಯಿನ್ ಫ್ರೇಮ್‌ವರ್ಕ್ ಆ ಹಾದಿಯಲ್ಲಿ ಒಂದು ವಿಶ್ವಾಸಾರ್ಹ ಸಂಗಾತಿಯಾಗಿದೆ.