Skill

জাভা কনকারেন্সি (Java Concurrency)

425

Java Concurrency হলো Java প্রোগ্রামিং ভাষায় একাধিক থ্রেড (threads) বা প্রসেসের মধ্যে সমান্তরাল কার্যকলাপ পরিচালনা করার জন্য ব্যবহৃত একটি ধারণা। এটি একটি গুরুত্বপূর্ণ বিষয় যা সফটওয়্যার উন্নয়নে পারফরম্যান্স এবং প্রতিক্রিয়া ক্ষমতা উন্নত করতে সাহায্য করে। Java Concurrency এর মাধ্যমে ডেভেলপাররা একাধিক কাজকে একসাথে সম্পাদন করতে পারেন, যার ফলে অ্যাপ্লিকেশনগুলো দ্রুত এবং কার্যকরীভাবে কাজ করে।


Java Concurrency: একটি বিস্তারিত বাংলা টিউটোরিয়াল

ভূমিকা

Concurrency বলতে বোঝায় একই সময়ে একাধিক কাজ সম্পাদন করার ক্ষমতা। Java Concurrency API ব্যবহার করে Java প্রোগ্রামে একাধিক থ্রেড তৈরি করা, পরিচালনা করা, এবং সিঙ্ক্রোনাইজ করা সম্ভব হয়। এটি মূলত প্রোগ্রামের কার্যকারিতা বাড়াতে এবং সময় বাঁচাতে ব্যবহার করা হয়। Java Concurrency API বিভিন্ন থ্রেড পরিচালনার টুল, লকার, এক্সিকিউটর সার্ভিস, এবং সিঙ্ক্রোনাইজেশন টুল সরবরাহ করে, যা ডেভেলপারদের কনকারেন্ট প্রোগ্রামিংকে সহজ করে।

থ্রেড কি?

থ্রেড হলো একটি ছোট এক্সিকিউশন ইউনিট, যা একটি প্রোগ্রামের মধ্যে আলাদা কাজ সম্পন্ন করতে ব্যবহৃত হয়। একটি প্রক্রিয়ার (Process) মধ্যে এক বা একাধিক থ্রেড থাকতে পারে। Java তে আপনি একাধিক থ্রেড তৈরি করে আলাদা কাজ একসঙ্গে সম্পন্ন করতে পারেন।

Java তে থ্রেড তৈরি করা

Java তে থ্রেড তৈরি করার দুটি প্রধান পদ্ধতি রয়েছে:

  1. Thread ক্লাস এক্সটেন্ড করা: আপনি Thread ক্লাস এক্সটেন্ড করে একটি থ্রেড তৈরি করতে পারেন।
  2. Runnable ইন্টারফেস ইমপ্লিমেন্ট করা: আপনি Runnable ইন্টারফেস ইমপ্লিমেন্ট করেও একটি থ্রেড তৈরি করতে পারেন।

১. Thread ক্লাস এক্সটেন্ড করা

Thread ক্লাস এক্সটেন্ড করে একটি নতুন থ্রেড তৈরি করতে নিচের ধাপগুলো অনুসরণ করা হয়:

class MyThread extends Thread {
    @Override
    public void run() {
        // থ্রেডের কাজ
        for (int i = 1; i <= 5; i++) {
            System.out.println(i + " from " + Thread.currentThread().getName());
            try {
                Thread.sleep(500); // 500ms এর জন্য থ্রেডকে স্লিপ করানো
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class ThreadExample {
    public static void main(String[] args) {
        // থ্রেড তৈরি এবং চালানো
        MyThread thread1 = new MyThread();
        MyThread thread2 = new MyThread();

        thread1.setName("Thread 1");
        thread2.setName("Thread 2");

        thread1.start();
        thread2.start();
    }
}

আউটপুট:

1 from Thread 1
1 from Thread 2
2 from Thread 1
2 from Thread 2
...

এই উদাহরণে, আমরা Thread ক্লাস এক্সটেন্ড করে দুটি থ্রেড তৈরি করেছি এবং তাদের আলাদা কাজ সম্পন্ন করাচ্ছি।

২. Runnable ইন্টারফেস ইমপ্লিমেন্ট করা

Runnable ইন্টারফেস ইমপ্লিমেন্ট করেও আপনি থ্রেড তৈরি করতে পারেন। এটি একটি বেশি ফ্লেক্সিবল পদ্ধতি:

class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println(i + " from " + Thread.currentThread().getName());
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class RunnableExample {
    public static void main(String[] args) {
        Thread thread1 = new Thread(new MyRunnable());
        Thread thread2 = new Thread(new MyRunnable());

        thread1.setName("Thread 1");
        thread2.setName("Thread 2");

        thread1.start();
        thread2.start();
    }
}

এই উদাহরণে, আমরা Runnable ইন্টারফেস ইমপ্লিমেন্ট করে দুটি থ্রেড তৈরি করেছি।

থ্রেড সিঙ্ক্রোনাইজেশন

একাধিক থ্রেড যখন একই রিসোর্সের উপর কাজ করে, তখন ডেটা ইনকনসিস্টেন্সি এড়াতে সিঙ্ক্রোনাইজেশন করা প্রয়োজন। synchronized কীওয়ার্ড ব্যবহার করে আপনি থ্রেডগুলোকে একটি নির্দিষ্ট ব্লকের এক্সিকিউশন ক্রম নিয়ন্ত্রণ করতে পারেন।

উদাহরণ:

class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

public class SynchronizationExample {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();

        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });

        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();

        System.out.println("Final count: " + counter.getCount());
    }
}

আউটপুট:

Final count: 2000

এই উদাহরণে, আমরা synchronized কীওয়ার্ড ব্যবহার করে increment মেথডটি সিঙ্ক্রোনাইজ করেছি, যাতে একই সময়ে একাধিক থ্রেড একসঙ্গে এই মেথডটি এক্সিকিউট না করতে পারে।

ExecutorService

ExecutorService হলো একটি শক্তিশালী Thread Pool ম্যানেজমেন্ট API, যা আপনাকে একাধিক থ্রেড পরিচালনা করতে সহায়তা করে। আপনি যখন বড় স্কেল প্রজেক্টে একাধিক থ্রেড পরিচালনা করতে চান, তখন ExecutorService ব্যবহার করা হয়।

উদাহরণ:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

class MyTask implements Runnable {
    @Override
    public void run() {
        System.out.println("Executing task in: " + Thread.currentThread().getName());
    }
}

public class ExecutorServiceExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(3); // 3 টি থ্রেডের পুল

        for (int i = 0; i < 5; i++) {
            executor.submit(new MyTask());
        }

        executor.shutdown();
    }
}

আউটপুট:

Executing task in: pool-1-thread-1
Executing task in: pool-1-thread-2
Executing task in: pool-1-thread-3
...

এই উদাহরণে, আমরা ExecutorService ব্যবহার করে একটি Thread Pool তৈরি করেছি এবং থ্রেডগুলোর মধ্যে কাজ ভাগ করেছি।

Future এবং Callable

Callable ইন্টারফেস Runnable এর মতো, কিন্তু এটি একটি রেজাল্ট ফেরত দেয়। Future ইন্টারফেস ব্যবহার করে আপনি Callable এর রেজাল্ট ধরতে পারেন।

উদাহরণ:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() {
        return 5 + 10;
    }
}

public class CallableExample {
    public static void main(String[] args) throws Exception {
        ExecutorService executor = Executors.newSingleThreadExecutor();

        Future<Integer> future = executor.submit(new MyCallable());

        System.out.println("Result: " + future.get());

        executor.shutdown();
    }
}

আউটপুট:

Result: 15

এই উদাহরণে, Callable ব্যবহার করে আমরা একটি কাজ করেছি যা একটি রেজাল্ট রিটার্ন করে। Future দিয়ে সেই রেজাল্টটি সংগ্রহ করেছি।

BlockingQueue

BlockingQueue হলো একটি কনকারেন্ট ডেটা স্ট্রাকচার, যা প্রযোজক-ভোক্তা (Producer-Consumer) প্যাটার্নে ব্যবহার করা হয়। এটি থ্রেডদের সিঙ্ক্রোনাইজ করে কাজ করতে সাহায্য করে।

উদাহরণ:

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class BlockingQueueExample {

    public static void main(String[] args) {
        BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(5);

        // Producer Thread
        Thread producer = new Thread(() -> {
            try {
                for (int i = 1; i <= 5; i++) {
                    System.out.println("Producing: " + i);
                    queue.put(i);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        // Consumer Thread
        Thread consumer = new Thread(() -> {
            try {
                for (int i = 1; i <= 5; i++) {
                    System.out.println("Consuming: " + queue.take());
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        producer.start();
        consumer.start();
    }
}

আউটপুট:

Producing: 1
Producing: 2
Consuming: 1
Producing: 3
Consuming: 2
...

এই উদাহরণে, আমরা BlockingQueue ব্যবহার করে Producer-Consumer প্যাটার্নের কাজ করেছি, যেখানে একটি থ্রেড প্রোডিউস করছে এবং আরেকটি থ্রেড কনজিউম করছে।

Java Concurrency এর সুবিধা

  1. একাধিক কাজ একসঙ্গে সম্পাদন করা: Java Concurrency ব্যবহার করে একই সময়ে একাধিক কাজ সম্পাদন করা সম্ভব।
  2. বড় স্কেল প্রজেক্টে কার্যকরী: বড় স্কেল অ্যাপ্লিকেশন বা সার্ভার সিস্টেমে একাধিক কাজ এবং প্রসেস পরিচালনা করার জন্য এটি কার্যকর।
  3. CPU ব্যবহার বাড়ানো: Concurrency প্রোগ্রামিং ব্যবহার করে CPU এর কার্যকারিতা বাড়ানো সম্ভব।
  4. Concurrency API: Java Concurrency API ডেভেলপারদের জন্য উন্নত থ্রেড ম্যানেজমেন্ট টুল সরবরাহ করে।

উপসংহার

Java Concurrency API ডেভেলপারদের জন্য একাধিক থ্রেড পরিচালনা করার একটি শক্তিশালী টুল। এটি ব্যবহার করে আপনি একাধিক কাজ একসঙ্গে সম্পাদন করতে পারেন এবং বড় প্রজেক্টের পারফরম্যান্স উন্নত করতে পারেন। Thread, Runnable, ExecutorService, Callable, এবং BlockingQueue এর মতো ক্লাস এবং ইন্টারফেস ব্যবহার করে আপনি সহজে কনকারেন্ট প্রোগ্রামিং করতে পারেন।

Java Concurrency হলো Java প্রোগ্রামিং ভাষায় একাধিক থ্রেড (threads) বা প্রসেসের মধ্যে সমান্তরাল কার্যকলাপ পরিচালনা করার জন্য ব্যবহৃত একটি ধারণা। এটি একটি গুরুত্বপূর্ণ বিষয় যা সফটওয়্যার উন্নয়নে পারফরম্যান্স এবং প্রতিক্রিয়া ক্ষমতা উন্নত করতে সাহায্য করে। Java Concurrency এর মাধ্যমে ডেভেলপাররা একাধিক কাজকে একসাথে সম্পাদন করতে পারেন, যার ফলে অ্যাপ্লিকেশনগুলো দ্রুত এবং কার্যকরীভাবে কাজ করে।


Java Concurrency: একটি বিস্তারিত বাংলা টিউটোরিয়াল

ভূমিকা

Concurrency বলতে বোঝায় একই সময়ে একাধিক কাজ সম্পাদন করার ক্ষমতা। Java Concurrency API ব্যবহার করে Java প্রোগ্রামে একাধিক থ্রেড তৈরি করা, পরিচালনা করা, এবং সিঙ্ক্রোনাইজ করা সম্ভব হয়। এটি মূলত প্রোগ্রামের কার্যকারিতা বাড়াতে এবং সময় বাঁচাতে ব্যবহার করা হয়। Java Concurrency API বিভিন্ন থ্রেড পরিচালনার টুল, লকার, এক্সিকিউটর সার্ভিস, এবং সিঙ্ক্রোনাইজেশন টুল সরবরাহ করে, যা ডেভেলপারদের কনকারেন্ট প্রোগ্রামিংকে সহজ করে।

থ্রেড কি?

থ্রেড হলো একটি ছোট এক্সিকিউশন ইউনিট, যা একটি প্রোগ্রামের মধ্যে আলাদা কাজ সম্পন্ন করতে ব্যবহৃত হয়। একটি প্রক্রিয়ার (Process) মধ্যে এক বা একাধিক থ্রেড থাকতে পারে। Java তে আপনি একাধিক থ্রেড তৈরি করে আলাদা কাজ একসঙ্গে সম্পন্ন করতে পারেন।

Java তে থ্রেড তৈরি করা

Java তে থ্রেড তৈরি করার দুটি প্রধান পদ্ধতি রয়েছে:

  1. Thread ক্লাস এক্সটেন্ড করা: আপনি Thread ক্লাস এক্সটেন্ড করে একটি থ্রেড তৈরি করতে পারেন।
  2. Runnable ইন্টারফেস ইমপ্লিমেন্ট করা: আপনি Runnable ইন্টারফেস ইমপ্লিমেন্ট করেও একটি থ্রেড তৈরি করতে পারেন।

১. Thread ক্লাস এক্সটেন্ড করা

Thread ক্লাস এক্সটেন্ড করে একটি নতুন থ্রেড তৈরি করতে নিচের ধাপগুলো অনুসরণ করা হয়:

class MyThread extends Thread {
    @Override
    public void run() {
        // থ্রেডের কাজ
        for (int i = 1; i <= 5; i++) {
            System.out.println(i + " from " + Thread.currentThread().getName());
            try {
                Thread.sleep(500); // 500ms এর জন্য থ্রেডকে স্লিপ করানো
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class ThreadExample {
    public static void main(String[] args) {
        // থ্রেড তৈরি এবং চালানো
        MyThread thread1 = new MyThread();
        MyThread thread2 = new MyThread();

        thread1.setName("Thread 1");
        thread2.setName("Thread 2");

        thread1.start();
        thread2.start();
    }
}

আউটপুট:

1 from Thread 1
1 from Thread 2
2 from Thread 1
2 from Thread 2
...

এই উদাহরণে, আমরা Thread ক্লাস এক্সটেন্ড করে দুটি থ্রেড তৈরি করেছি এবং তাদের আলাদা কাজ সম্পন্ন করাচ্ছি।

২. Runnable ইন্টারফেস ইমপ্লিমেন্ট করা

Runnable ইন্টারফেস ইমপ্লিমেন্ট করেও আপনি থ্রেড তৈরি করতে পারেন। এটি একটি বেশি ফ্লেক্সিবল পদ্ধতি:

class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println(i + " from " + Thread.currentThread().getName());
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class RunnableExample {
    public static void main(String[] args) {
        Thread thread1 = new Thread(new MyRunnable());
        Thread thread2 = new Thread(new MyRunnable());

        thread1.setName("Thread 1");
        thread2.setName("Thread 2");

        thread1.start();
        thread2.start();
    }
}

এই উদাহরণে, আমরা Runnable ইন্টারফেস ইমপ্লিমেন্ট করে দুটি থ্রেড তৈরি করেছি।

থ্রেড সিঙ্ক্রোনাইজেশন

একাধিক থ্রেড যখন একই রিসোর্সের উপর কাজ করে, তখন ডেটা ইনকনসিস্টেন্সি এড়াতে সিঙ্ক্রোনাইজেশন করা প্রয়োজন। synchronized কীওয়ার্ড ব্যবহার করে আপনি থ্রেডগুলোকে একটি নির্দিষ্ট ব্লকের এক্সিকিউশন ক্রম নিয়ন্ত্রণ করতে পারেন।

উদাহরণ:

class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

public class SynchronizationExample {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();

        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });

        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();

        System.out.println("Final count: " + counter.getCount());
    }
}

আউটপুট:

Final count: 2000

এই উদাহরণে, আমরা synchronized কীওয়ার্ড ব্যবহার করে increment মেথডটি সিঙ্ক্রোনাইজ করেছি, যাতে একই সময়ে একাধিক থ্রেড একসঙ্গে এই মেথডটি এক্সিকিউট না করতে পারে।

ExecutorService

ExecutorService হলো একটি শক্তিশালী Thread Pool ম্যানেজমেন্ট API, যা আপনাকে একাধিক থ্রেড পরিচালনা করতে সহায়তা করে। আপনি যখন বড় স্কেল প্রজেক্টে একাধিক থ্রেড পরিচালনা করতে চান, তখন ExecutorService ব্যবহার করা হয়।

উদাহরণ:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

class MyTask implements Runnable {
    @Override
    public void run() {
        System.out.println("Executing task in: " + Thread.currentThread().getName());
    }
}

public class ExecutorServiceExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(3); // 3 টি থ্রেডের পুল

        for (int i = 0; i < 5; i++) {
            executor.submit(new MyTask());
        }

        executor.shutdown();
    }
}

আউটপুট:

Executing task in: pool-1-thread-1
Executing task in: pool-1-thread-2
Executing task in: pool-1-thread-3
...

এই উদাহরণে, আমরা ExecutorService ব্যবহার করে একটি Thread Pool তৈরি করেছি এবং থ্রেডগুলোর মধ্যে কাজ ভাগ করেছি।

Future এবং Callable

Callable ইন্টারফেস Runnable এর মতো, কিন্তু এটি একটি রেজাল্ট ফেরত দেয়। Future ইন্টারফেস ব্যবহার করে আপনি Callable এর রেজাল্ট ধরতে পারেন।

উদাহরণ:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() {
        return 5 + 10;
    }
}

public class CallableExample {
    public static void main(String[] args) throws Exception {
        ExecutorService executor = Executors.newSingleThreadExecutor();

        Future<Integer> future = executor.submit(new MyCallable());

        System.out.println("Result: " + future.get());

        executor.shutdown();
    }
}

আউটপুট:

Result: 15

এই উদাহরণে, Callable ব্যবহার করে আমরা একটি কাজ করেছি যা একটি রেজাল্ট রিটার্ন করে। Future দিয়ে সেই রেজাল্টটি সংগ্রহ করেছি।

BlockingQueue

BlockingQueue হলো একটি কনকারেন্ট ডেটা স্ট্রাকচার, যা প্রযোজক-ভোক্তা (Producer-Consumer) প্যাটার্নে ব্যবহার করা হয়। এটি থ্রেডদের সিঙ্ক্রোনাইজ করে কাজ করতে সাহায্য করে।

উদাহরণ:

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class BlockingQueueExample {

    public static void main(String[] args) {
        BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(5);

        // Producer Thread
        Thread producer = new Thread(() -> {
            try {
                for (int i = 1; i <= 5; i++) {
                    System.out.println("Producing: " + i);
                    queue.put(i);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        // Consumer Thread
        Thread consumer = new Thread(() -> {
            try {
                for (int i = 1; i <= 5; i++) {
                    System.out.println("Consuming: " + queue.take());
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        producer.start();
        consumer.start();
    }
}

আউটপুট:

Producing: 1
Producing: 2
Consuming: 1
Producing: 3
Consuming: 2
...

এই উদাহরণে, আমরা BlockingQueue ব্যবহার করে Producer-Consumer প্যাটার্নের কাজ করেছি, যেখানে একটি থ্রেড প্রোডিউস করছে এবং আরেকটি থ্রেড কনজিউম করছে।

Java Concurrency এর সুবিধা

  1. একাধিক কাজ একসঙ্গে সম্পাদন করা: Java Concurrency ব্যবহার করে একই সময়ে একাধিক কাজ সম্পাদন করা সম্ভব।
  2. বড় স্কেল প্রজেক্টে কার্যকরী: বড় স্কেল অ্যাপ্লিকেশন বা সার্ভার সিস্টেমে একাধিক কাজ এবং প্রসেস পরিচালনা করার জন্য এটি কার্যকর।
  3. CPU ব্যবহার বাড়ানো: Concurrency প্রোগ্রামিং ব্যবহার করে CPU এর কার্যকারিতা বাড়ানো সম্ভব।
  4. Concurrency API: Java Concurrency API ডেভেলপারদের জন্য উন্নত থ্রেড ম্যানেজমেন্ট টুল সরবরাহ করে।

উপসংহার

Java Concurrency API ডেভেলপারদের জন্য একাধিক থ্রেড পরিচালনা করার একটি শক্তিশালী টুল। এটি ব্যবহার করে আপনি একাধিক কাজ একসঙ্গে সম্পাদন করতে পারেন এবং বড় প্রজেক্টের পারফরম্যান্স উন্নত করতে পারেন। Thread, Runnable, ExecutorService, Callable, এবং BlockingQueue এর মতো ক্লাস এবং ইন্টারফেস ব্যবহার করে আপনি সহজে কনকারেন্ট প্রোগ্রামিং করতে পারেন।

Promotion

Are you sure to start over?

Loading...