线程是独立程序的执行路径。 在java中,每个线程都扩展java.lang.Thread类或实现java.lang.Runnable。
多线程是指在一个任务中同时执行两个或多个线程。在多线程中,每个任务可以具有多个线程,并且这些线程可以异步或同步地并行运行。 您可以在我在此处撰写的有关多线程的另一教程中找到有关线程和多线程的更多信息。
1.什么是线程池
线程池代表一组执行任务的工作线程,每个线程可以多次重用。
如果在所有线程都处于活动状态时提交了新任务,则它们将在队列中等待直到线程可用。
线程池实现在内部使用LinkedBlockingQueue
在队列中添加和删除任务。
我们通常想要的是一个工作队列,该队列与一组固定的工作线程组合在一起,该工作线程使用wait()
和notify()
来向等待线程发出新工作到达的信号。
以下示例显示了一个简单的工作队列,该队列是Runnable
对象的队列。
尽管没有特别要求Thread API使用Runnable
接口,但这是调度程序和工作队列的常用约定。
package tutorials;
import java.util.concurrent.LinkedBlockingQueue;
public class ThreadPool {
private final int nThreads;
private final PoolWorker[] threads;
private final LinkedBlockingQueue queue;
public ThreadPool(int nThreads) {
this.nThreads = nThreads;
queue = new LinkedBlockingQueue();
threads = new PoolWorker[nThreads];
for (int i = 0; i < nThreads; i++) {
threads[i] = new PoolWorker();
threads[i].start();
}
}
public void execute(Runnable task) {
synchronized (queue) {
queue.add(task);
queue.notify();
}
}
private class PoolWorker extends Thread {
public void run() {
Runnable task;
while (true) {
synchronized (queue) {
while (queue.isEmpty()) {
try {
queue.wait();
} catch (InterruptedException e) {
System.out.println("An error occurred while queue is waiting: " + e.getMessage());
}
}
task = queue.poll();
}
// If we don't catch RuntimeException,
// the pool could leak threads
try {
task.run();
} catch (RuntimeException e) {
System.out.println("Thread pool is interrupted due to an issue: " + e.getMessage());
}
}
}
}
}
在处理队列时,使用同步块很重要,以控制线程对队列的访问。
package tutorials;
public class Task implements Runnable {
private int num;
public Task(int n) {
num = n;
}
public void run() {
System.out.println("Task " + num + " is running.");
}
}
import tutorials.Task;
import tutorials.ThreadPool;
public class Main {
public static void main(String[] args) {
ThreadPool pool = new ThreadPool(7);
for (int i = 0; i < 5; i++) {
Task task = new Task(i);
pool.execute(task);
}
}
在上面的示例中,我们使用notify()
而不是notifyAll()
。 因为notify()
具有比notifyAll()
更理想的性能特征; 特别是, notify()
导致更少的上下文切换,这在服务器应用程序中很重要。 但是重要的是要确保在其他情况下使用notify()
,因为与notify()
关联存在细微的风险,并且仅在某些特定条件下使用它才是合适的。
下图演示了以上示例中的线程池设计。
图1。 线程池设计
2.有效使用线程池
线程池是一种用于构造多线程应用程序的强大机制,但并非没有风险。 用线程池构建的应用程序可能具有与任何其他多线程应用程序相同的并发风险,例如死锁 , 资源崩溃,同步或并发错误,线程泄漏和请求重载 。
以下是一些要点:
- 不要将同步等待其他任务的任务排队,因为这可能导致死锁。
- 如果任务需要等待诸如I / O之类的资源,请指定最大等待时间,然后使任务执行失败或重新排队。 这保证了通过释放线程执行可能成功完成的另一个任务将取得一些进展。
- 有效地调整线程池的大小,并了解线程太少或线程太多都会导致问题。 线程池的最佳大小取决于可用处理器的数量以及工作队列上任务的性质。
3.结论
线程池对于组织服务器应用程序很有用,并且正确地实现它以防止任何问题(例如死锁和wait()
或notify()
使用的复杂性)非常重要。 因此,建议考虑使用util.concurrent
中的Executor
类之一,例如ThreadPoolExecutor
,而不是从头开始编写线程池。 如果要求创建线程来处理短期任务,则可以考虑使用线程池。