هم زمانی در جاوا
همزمانی (Multithreading) و همزمانی در جاوا (Concurrency) یکی از ویژگیهای قدرتمند این زبان است که به شما این امکان را میدهد که برنامهها را به صورت موازی اجرا کنید. با استفاده از همزمانی، میتوانید عملیات مختلف را به صورت همزمان انجام دهید و در نتیجه بهرهوری و کارایی برنامه را بهبود بخشید.
در جاوا، همزمانی و چندریختی با استفاده از ت线程 (Threads) و مدیریت همزمانی (Concurrency Management) قابل پیادهسازی است. جاوا امکاناتی برای ساخت و مدیریت رشتههای مختلف در یک برنامه و همزمان سازی دادهها فراهم میکند.
1. رشتهها (Threads) در جاوا
رشتهها در جاوا واحدهای مستقل اجرایی هستند که میتوانند به صورت همزمان در یک برنامه اجرا شوند. جاوا به صورت پیشفرض از برنامهنویسی چندریختهای پشتیبانی میکند. هر برنامه جاوا به طور پیشفرض با یک رشته اصلی (Main Thread) اجرا میشود، و شما میتوانید رشتههای جدیدی ایجاد کرده و آنها را همزمان اجرا کنید.
1.1. ساخت رشته در جاوا
برای ایجاد یک رشته جدید در جاوا، میتوانید از دو روش استفاده کنید:
- پیادهسازی رابط Runnable
- ارثبری از کلاس Thread
1.1.1. استفاده از کلاس Thread:
public void run() {
System.out.println("این رشته در حال اجرا است.");
}
}
public class ThreadExample {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start(); // شروع رشته
}
}
در اینجا، کلاس MyThread از Thread ارث بری کرده است و متد run() را برای انجام کار مورد نظر بازنویسی کردهایم. سپس با استفاده از start()، رشته اجرا میشود.
1.1.2. استفاده از رابط Runnable:
public void run() {
System.out.println("این رشته از طریق رابط Runnable اجرا میشود.");
}
}
public class RunnableExample {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start(); // شروع رشته
}
}
در این روش، شما یک کلاس Runnable میسازید و متد run() را در آن پیادهسازی میکنید. سپس یک شیء از کلاس Thread ایجاد کرده و به آن Runnable را ارسال میکنید.
2. مدیریت همزمانی (Concurrency Management)
هنگامی که چندین رشته به منابع مشترک دسترسی دارند، ممکن است منجر به بروز مشکلات همزمانی مانند شرایط رقابتی (Race Condition) شود. برای جلوگیری از این مشکلات، جاوا ابزارهایی مانند همزمان سازی (Synchronization) و قفلها (Locks) را فراهم کرده است.
2.1. همزمان سازی (Synchronization)
برای اطمینان از این که تنها یک رشته به منابع مشترک دسترسی دارد، میتوان از همزمان سازی استفاده کرد. این کار با استفاده از کلمهکلیدی synchronized در جاوا انجام میشود.
مثال همزمان سازی:
private int count = 0;
// همزمان سازی برای جلوگیری از دسترسی همزمان به متد
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
public class SynchronizedExample {
public static void main(String[] args) throws InterruptedException {
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(); // منتظر میمانیم تا رشته t1 به اتمام برسد
t2.join(); // منتظر میمانیم تا رشته t2 به اتمام برسد
System.out.println("شمارش نهایی: " + counter.getCount());
}
}
در این مثال، از synchronized برای همزمان سازی دسترسی به متد increment() استفاده میشود تا از شرایط رقابتی جلوگیری شود.
2.2. قفلها (Locks)
در جاوا، به جز استفاده از synchronized، میتوان از قفلها نیز برای کنترل همزمانی استفاده کرد. قفلها به طور دقیقتری امکان کنترل دسترسی به منابع مشترک را فراهم میکنند.
جاوا از کلاسهای ReentrantLock و ReadWriteLock برای مدیریت قفلها استفاده میکند.
مثال استفاده از ReentrantLock:
import java.util.concurrent.locks.ReentrantLock;
class Counter {
private int count = 0;
private Lock lock = new ReentrantLock();
public void increment() {
lock.lock(); // گرفتن قفل
try {
count++;
} finally {
lock.unlock(); // آزاد کردن قفل
}
}
public int getCount() {
return count;
}
}
public class LockExample {
public static void main(String[] args) throws InterruptedException {
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(); // منتظر میمانیم تا رشته t1 به اتمام برسد
t2.join(); // منتظر میمانیم تا رشته t2 به اتمام برسد
System.out.println("شمارش نهایی: " + counter.getCount());
}
}
در اینجا، از ReentrantLock برای کنترل دسترسی به متد increment() استفاده میشود. بهطور کلی، استفاده از قفلها کنترل دقیقتری نسبت به synchronized فراهم میکند.
3. مدیریت رشتهها با استفاده از ExecutorService
برای مدیریت بهتر رشتهها، جاوا از ExecutorService به عنوان یک ابزار مدیریت پیشرفته استفاده میکند. این ابزار به شما این امکان را میدهد که به راحتی رشتهها را ایجاد کرده و آنها را مدیریت کنید.
مثال استفاده از ExecutorService:
public class ExecutorServiceExample {
public static void main(String[] args) throws InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.submit(() -> {
System.out.println("رشته اول در حال اجرا");
});
executor.submit(() -> {
System.out.println("رشته دوم در حال اجرا");
});
executor.shutdown(); // خاموش کردن Executor پس از اتمام تمام وظایف
}
}
در اینجا، از ExecutorService برای مدیریت و اجرای رشتهها به صورت سادهتر و کنترلشده استفاده میشود.
4. مدیریت دسترسی به دادههای مشترک (Atomic Operations)
جاوا همچنین از عملیات اتمی برای جلوگیری از مشکلات همزمانی استفاده میکند. از این نوع عملیاتها میتوان برای انجام تغییرات در دادههای مشترک بدون نیاز به قفل استفاده کرد.
مثال استفاده از AtomicInteger:
public class AtomicExample {
public static void main(String[] args) throws InterruptedException {
AtomicInteger atomicCounter = new AtomicInteger(0);
// ایجاد چند رشته برای افزایش شمارش
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
atomicCounter.incrementAndGet();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
atomicCounter.incrementAndGet();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("شمارش نهایی: " + atomicCounter.get());
}
}
در این مثال، از AtomicInteger برای انجام عملیات اتمی روی شمارشگر استفاده میشود که موجب جلوگیری از مشکلات همزمانی بدون نیاز به قفلگذاری میشود.
نتیجهگیری:
همزمانی در جاوا یکی از ویژگیهای مهم است که میتواند به بهبود کارایی برنامهها کمک کند. با استفاده از ابزارهای مختلف مانند Thread, Runnable, ExecutorService, قفلها، و عملیات اتمی، میتوان به راحتی برنامههایی با عملکرد بالا و بدون مشکلات همزمانی نوشت. مدیریت همزمانی به طور مؤثر میتواند به بهینهسازی منابع و زمان اجرای برنامهها منجر شود.
