优化方面
- 信号量初始化参数设置:
- 合理设置许可数量:根据实际资源数量和预期并发访问量设置信号量的初始许可数量。如果许可数量设置过小,会导致大量线程等待,降低系统吞吐量;设置过大则可能无法有效控制资源访问,导致资源竞争和性能问题。
- 考虑公平性:可以通过构造函数设置信号量是否公平。公平信号量会按照线程请求的顺序来分配许可,避免线程饥饿,但可能会因为线程切换带来额外开销;非公平信号量则允许线程在许可可用时立即获取,可能会使一些线程长时间等待,但通常性能更好。
- 结合其他并发工具类使用:
- 与线程池结合:使用线程池来管理并发任务,线程池可以控制并发线程的数量,避免过多线程同时竞争信号量,减少线程上下文切换开销。
- 结合阻塞队列:可以使用阻塞队列来缓存任务,当信号量不可用时,任务可以在阻塞队列中等待,而不是直接在信号量上等待,从而提高系统的整体性能。
代码示例及优化思路
- 简单信号量使用示例
import java.util.concurrent.Semaphore;
public class SimpleSemaphoreExample {
private static final int RESOURCE_COUNT = 3;
private static final Semaphore semaphore = new Semaphore(RESOURCE_COUNT);
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " 获取到信号量,开始访问资源");
// 模拟资源访问
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " 访问资源结束,释放信号量");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}).start();
}
}
}
- 优化思路:在这个简单示例中,信号量初始许可数量为3,表示最多允许3个线程同时访问资源。每个线程在访问资源前调用
acquire()
获取信号量,访问结束后调用release()
释放信号量。这里可以进一步优化信号量的初始化参数,比如根据实际负载情况动态调整许可数量。
- 结合线程池优化示例
import java.util.concurrent.*;
public class SemaphoreWithThreadPoolExample {
private static final int RESOURCE_COUNT = 3;
private static final Semaphore semaphore = new Semaphore(RESOURCE_COUNT);
private static final ExecutorService executorService = Executors.newFixedThreadPool(5);
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
executorService.submit(() -> {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " 获取到信号量,开始访问资源");
// 模拟资源访问
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " 访问资源结束,释放信号量");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
});
}
executorService.shutdown();
}
}
- 优化思路:通过引入线程池
ExecutorService
,将任务提交到线程池执行。线程池控制了并发线程的数量,避免了过多线程同时竞争信号量。这里使用了固定大小为5的线程池,可以根据实际情况调整线程池大小,以达到最佳性能。同时,还可以结合Future
等工具类来处理任务的返回结果。
- 结合阻塞队列优化示例
import java.util.concurrent.*;
public class SemaphoreWithBlockingQueueExample {
private static final int RESOURCE_COUNT = 3;
private static final Semaphore semaphore = new Semaphore(RESOURCE_COUNT);
private static final BlockingQueue<Runnable> taskQueue = new ArrayBlockingQueue<>(10);
private static final ExecutorService executorService = new ThreadPoolExecutor(
5,
10,
10L,
TimeUnit.SECONDS,
taskQueue
);
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
executorService.submit(() -> {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " 获取到信号量,开始访问资源");
// 模拟资源访问
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " 访问资源结束,释放信号量");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
});
}
executorService.shutdown();
}
}
- 优化思路:在结合线程池的基础上,引入阻塞队列
BlockingQueue
。当线程池中的线程都在忙碌时,新的任务会被放入阻塞队列中等待。这样可以避免过多任务直接竞争信号量,减少线程的无效等待,提高系统整体性能。这里使用了ArrayBlockingQueue
作为阻塞队列,并设置了队列容量为10,可以根据实际情况调整队列容量。