Java-তে Multiple Threads এর মধ্যে I/O Synchronization একটি গুরুত্বপূর্ণ কৌশল, বিশেষত যখন একাধিক থ্রেড একই সময়ে একটি ফাইল বা অন্যান্য I/O রিসোর্স অ্যাক্সেস করতে চায়। এই ধরনের পরিস্থিতিতে Thread Safety বজায় রাখা এবং Data Integrity নিশ্চিত করা অত্যন্ত গুরুত্বপূর্ণ, যাতে কোনো থ্রেড অপর থ্রেডের কাজকে বিরক্ত না করে এবং ডেটা খারাপ না হয়।
I/O Synchronization এর উদ্দেশ্য:
I/O অপারেশনগুলি সাধারণত Blocking হয়, অর্থাৎ যখন একটি থ্রেড I/O অপারেশন চালায় (যেমন ফাইল পড়া বা লেখা), তখন অন্যান্য থ্রেডগুলি অপেক্ষা করে থাকে। এই কারণে, একাধিক থ্রেড একসাথে I/O অপারেশন করলে data inconsistency এবং race conditions সৃষ্টি হতে পারে। তাই সিঙ্ক্রোনাইজেশন (Synchronization) ব্যবহার করা হয় যাতে এক সময়ে একমাত্র একটি থ্রেড I/O অপারেশন করতে পারে।
Java I/O Synchronization পদ্ধতি:
synchronizedব্লক: যখন একাধিক থ্রেড একই I/O অপারেশন (যেমন ফাইল পড়া বা লেখা) একসাথে করতে চায়, তখনsynchronizedব্লক ব্যবহার করা হয়। এটি থ্রেডের মধ্যে একসাথে একটাই থ্রেডকে কাজ করতে দেয়।ReentrantLock:ReentrantLockব্যবহৃত হয় যখন একাধিক থ্রেডের মধ্যে আরও নির্ভরযোগ্য এবং ফাইন কন্ট্রোল প্রয়োজন, বিশেষত I/O অপারেশন চলাকালীন।- File Locks:
FileLockব্যবহার করে আপনি ফাইল লক করতে পারেন যাতে একটি থ্রেড শুধুমাত্র ফাইল অ্যাক্সেস করতে পারে এবং অন্যান্য থ্রেডগুলি অপেক্ষা করবে।
Thread Synchronization উদাহরণ:
Example 1: Basic I/O Synchronization Using synchronized Keyword
এই উদাহরণে, দুটি থ্রেড একটি ফাইলের মধ্যে লেখা শুরু করে এবং সিঙ্ক্রোনাইজেশন নিশ্চিত করা হয় যাতে এক সময়ে শুধু একটি থ্রেড ফাইল লেখার কাজ করতে পারে।
import java.io.*;
public class ThreadSyncFileWriting {
public static void main(String[] args) throws InterruptedException {
// Shared resource (file)
File file = new File("output.txt");
// Thread 1 - Writing to file
Thread thread1 = new Thread(new FileWriterTask(file));
// Thread 2 - Writing to file
Thread thread2 = new Thread(new FileWriterTask(file));
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("Writing operation completed.");
}
}
class FileWriterTask implements Runnable {
private File file;
public FileWriterTask(File file) {
this.file = file;
}
@Override
public void run() {
synchronized (file) { // Synchronizing I/O operations to avoid race condition
try (BufferedWriter writer = new BufferedWriter(new FileWriter(file, true))) {
for (int i = 0; i < 5; i++) {
writer.write(Thread.currentThread().getName() + " - Writing line " + (i + 1));
writer.newLine();
System.out.println(Thread.currentThread().getName() + " is writing...");
Thread.sleep(100); // Simulate some delay
}
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}
}
ব্যাখ্যা:
- দুটি থ্রেড একই ফাইলের মধ্যে ডেটা লেখার চেষ্টা করছে।
synchronizedব্লক ব্যবহার করা হয়েছে যাতেFileWriterTaskক্লাসের একটি থ্রেড এক সময়ে শুধুমাত্র ফাইলটি লিখতে পারে, অন্য থ্রেডকে অপেক্ষা করতে হবে।- ফাইল লেখার সময় Thread.sleep(100) দ্বারা সামান্য বিলম্ব তৈরি করা হয়েছে, যাতে একই সময়ে দুটো থ্রেড একে অপরকে প্রভাবিত করতে পারে।
আউটপুট:
Thread-0 is writing...
Thread-1 is writing...
Thread-0 is writing...
Thread-1 is writing...
Thread-0 is writing...
Thread-1 is writing...
Thread-0 is writing...
Thread-1 is writing...
Thread-0 is writing...
Thread-1 is writing...
Writing operation completed.
এখানে দেখানো হয়েছে যে, থ্রেডগুলো একে একে ফাইলের মধ্যে লেখা করছে, এবং synchronized ব্যবহার করার ফলে একই সময়ে দুটি থ্রেড একসাথে ফাইল লেখার কাজ করতে পারেনি।
Example 2: Using ReentrantLock for I/O Synchronization
এখানে ReentrantLock ব্যবহার করা হয়েছে যাতে ফাইল লেখার সময় আরও নিখুঁত কন্ট্রোল পাওয়া যায়। এটি Lock ইন্টারফেসের একটি কার্যকর বাস্তবায়ন এবং এটি synchronized ব্লকের তুলনায় আরও বড় পরিসরে কন্ট্রোল প্রদান করে।
import java.io.*;
import java.util.concurrent.locks.*;
public class LockFileWriting {
public static void main(String[] args) throws InterruptedException {
// Shared resource (file)
File file = new File("output.txt");
Lock fileLock = new ReentrantLock(); // ReentrantLock initialization
// Thread 1 - Writing to file
Thread thread1 = new Thread(new LockWriterTask(file, fileLock));
// Thread 2 - Writing to file
Thread thread2 = new Thread(new LockWriterTask(file, fileLock));
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("Writing operation completed.");
}
}
class LockWriterTask implements Runnable {
private File file;
private Lock lock;
public LockWriterTask(File file, Lock lock) {
this.file = file;
this.lock = lock;
}
@Override
public void run() {
lock.lock(); // Acquire the lock
try (BufferedWriter writer = new BufferedWriter(new FileWriter(file, true))) {
for (int i = 0; i < 5; i++) {
writer.write(Thread.currentThread().getName() + " - Writing line " + (i + 1));
writer.newLine();
System.out.println(Thread.currentThread().getName() + " is writing...");
Thread.sleep(100); // Simulate some delay
}
} catch (IOException | InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); // Always unlock after finishing
}
}
}
ব্যাখ্যা:
- এখানে
ReentrantLockব্যবহৃত হয়েছে যাlock()মেথডের মাধ্যমে ফাইল লেখার জন্য lock করে দেয় এবং কাজ শেষ হলেunlock()মেথডের মাধ্যমে থ্রেডটি আনলক করে। - এটি
synchronizedব্লকের মতো কাজ করে, তবে আপনি যখন চান তখন আরো বেশি কাস্টমাইজেশন (যেমনtryLock(),lockInterruptibly()) করতে পারেন।
আউটপুট:
Thread-0 is writing...
Thread-1 is writing...
Thread-0 is writing...
Thread-1 is writing...
Thread-0 is writing...
Thread-1 is writing...
Thread-0 is writing...
Thread-1 is writing...
Thread-0 is writing...
Thread-1 is writing...
Writing operation completed.
Multiple Threads এর মধ্যে I/O Synchronization এর জন্য Best Practices:
synchronizedব্লক ব্যবহার করুন:- যদি শুধুমাত্র একাধিক থ্রেডের মধ্যে একটি ইনপুট স্ট্রীম বা আউটপুট স্ট্রীমের shared access থাকে, তবে
synchronizedব্লক ব্যবহার করা খুবই কার্যকরী।
- যদি শুধুমাত্র একাধিক থ্রেডের মধ্যে একটি ইনপুট স্ট্রীম বা আউটপুট স্ট্রীমের shared access থাকে, তবে
ReentrantLockব্যবহার করুন:- আরো উন্নত কন্ট্রোল এবং ফাইন কন্ট্রোল ব্যবস্থার জন্য
ReentrantLockব্যবহার করা যেতে পারে, যা আরও অনেক বেশি অপশন এবং সুবিধা প্রদান করে যেমনtryLock()এবংlockInterruptibly()।
- আরো উন্নত কন্ট্রোল এবং ফাইন কন্ট্রোল ব্যবস্থার জন্য
- Thread Safe Data Structures:
- Concurrent Collections এবং BlockingQueue এর মতো থ্রেড সেফ ডেটা স্ট্রাকচার ব্যবহার করুন যদি আপনার প্রোগ্রামে একাধিক থ্রেডের মধ্যে ডেটা ভাগ করা হয়।
- Minimize Synchronized Code:
- যতটা সম্ভব critical section (যতটুকু কোড যা থ্রেডের মধ্যে একসাথে কাজ করবে) ছোট রাখুন, যাতে আপনি থ্রেডের প্রতিযোগিতাকে কমাতে পারেন।
- Avoid Nested Locks:
- Nested locks থেকে বিরত থাকুন, কারণ এটি deadlock সৃষ্টি করতে পারে। নিশ্চিত করুন যে থ্রেডগুলি শুধুমাত্র একটি lock অর্জন করছে একবারে।
- I/O Synchronization হল একাধিক থ্রেডের মধ্যে I/O অপারেশন সমন্বয় করার জন্য গুরুত্বপূর্ণ একটি প্রক্রিয়া, যেখানে race conditions এবং data inconsistency এড়ানোর জন্য সঠিক synchronization প্রয়োজন।
synchronizedব্লক এবংReentrantLockব্যবহারের মাধ্যমে আপনি thread safety নিশ্চিত করতে পারেন এবং ডেটা integrity রক্ষা করতে পারেন।