Java教程-Java线程池

Java线程池
Java线程池代表了一组工作线程,它们等待任务并可以重复使用。
在线程池中,会创建一个固定大小的线程组。从线程池中取出一个线程,并由服务提供者分配一个任务。任务完成后,线程会再次放回线程池中。
线程池的方法
newFixedThreadPool(int s):该方法创建一个大小固定为s的线程池。
newCachedThreadPool():该方法创建一个新的线程池,在需要时创建新线程,但仍然使用以前创建的线程,只要它们可用。
newSingleThreadExecutor():该方法创建一个新的线程。
Java线程池的优势
更好的性能:它节省时间,因为不需要创建新的线程。
实时使用
它在Servlet和JSP中被使用,容器会创建一个线程池来处理请求。
Java线程池的示例
让我们通过使用ExecutorService和Executors来看一个简单的Java线程池示例。
文件:WorkerThread.java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
class WorkerThread implements Runnable {
private String message;
public WorkerThread(String s){
this.message=s;
}
public void run() {
System.out.println(Thread.currentThread().getName()+" (Start) message = "+message);
processmessage();//调用使线程休眠 2 秒的 processmessage 方法
System.out.println(Thread.currentThread().getName()+" (End)");//打印线程名称
}
private void processmessage() {
try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }
}
}
文件:TestThreadPool.java
public class TestThreadPool {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(5);//创建一个5线程池
for (int i = 0; i < 10; i++) {
Runnable worker = new WorkerThread("" + i);
executor.execute(worker);//调用ExecutorService的execute方法
}
executor.shutdown();
while (!executor.isTerminated()) { }
System.out.println("Finished all threads");
}
}
输出:
pool-1-thread-1 (Start) message = 0
pool-1-thread-2 (Start) message = 1
pool-1-thread-3 (Start) message = 2
pool-1-thread-5 (Start) message = 4
pool-1-thread-4 (Start) message = 3
pool-1-thread-2 (End)
pool-1-thread-2 (Start) message = 5
pool-1-thread-1 (End)
pool-1-thread-1 (Start) message = 6
pool-1-thread-3 (End)
pool-1-thread-3 (Start) message = 7
pool-1-thread-4 (End)
pool-1-thread-4 (Start) message = 8
pool-1-thread-5 (End)
pool-1-thread-5 (Start) message = 9
pool-1-thread-2 (End)
pool-1-thread-1 (End)
pool-1-thread-4 (End)
pool-1-thread-3 (End)
pool-1-thread-5 (End)
Finished all threads
线程池示例:2
让我们看另一个线程池的示例。
文件名:ThreadPoolExample.java
// 重要的导入语句
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.text.SimpleDateFormat;
class Tasks implements Runnable
{
private String taskName;
// 类任务的构造函数
public Tasks(String str)
{
// 初始化字段taskName
taskName = str;
}
// 打印任务名称然后休眠 1 秒
// 整个过程重复五次
public void run()
{
try
{
for (int j = 0; j <= 5; j++)
{
if (j == 0)
{
Date dt = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("hh : mm : ss");
//打印每个任务的初始化时间
System.out.println("Initialization time for the task name: "+ taskName + " = " + sdf.format(dt));
}
else
{
Date dt = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("hh : mm : ss");
// 打印每个任务的执行时间
System.out.println("Time of execution for the task name: " + taskName + " = " +sdf.format(dt));
}
// 1000 毫秒 = 1 秒
Thread.sleep(1000);
}
System.out.println(taskName + " is complete.");
}
catch(InterruptedException ie)
{
ie.printStackTrace();
}
}
}
public class ThreadPoolExample
{
// 线程池最大线程数
static final int MAX_TH = 3;
// 主要方法
public static void main(String argvs[])
{
// 创建五个新任务
Runnable rb1 = new Tasks("task 1");
Runnable rb2 = new Tasks("task 2");
Runnable rb3 = new Tasks("task 3");
Runnable rb4 = new Tasks("task 4");
Runnable rb5 = new Tasks("task 5");
// 创建一个 MAX_TH 数量的线程池
// 线程大小池大小是固定的
ExecutorService pl = Executors.newFixedThreadPool(MAX_TH);
// 将 Task 对象传递到池中执行(第 3 步)
pl.execute(rb1);
pl.execute(rb2);
pl.execute(rb3);
pl.execute(rb4);
pl.execute(rb5);
// 池关闭
pl.shutdown();
}
}
输出:
Initialization time for the task name: task 1 = 06 : 13 : 02
Initialization time for the task name: task 2 = 06 : 13 : 02
Initialization time for the task name: task 3 = 06 : 13 : 02
Time of execution for the task name: task 1 = 06 : 13 : 04
Time of execution for the task name: task 2 = 06 : 13 : 04
Time of execution for the task name: task 3 = 06 : 13 : 04
Time of execution for the task name: task 1 = 06 : 13 : 05
Time of execution for the task name: task 2 = 06 : 13 : 05
Time of execution for the task name: task 3 = 06 : 13 : 05
Time of execution for the task name: task 1 = 06 : 13 : 06
Time of execution for the task name: task 2 = 06 : 13 : 06
Time of execution for the task name: task 3 = 06 : 13 : 06
Time of execution for the task name: task 1 = 06 : 13 : 07
Time of execution for the task name: task 2 = 06 : 13 : 07
Time of execution for the task name: task 3 = 06 : 13 : 07
Time of execution for the task name: task 1 = 06 : 13 : 08
Time of execution for the task name: task 2 = 06 : 13 : 08
Time of execution for the task name: task 3 = 06 : 13 : 08
task 2 is complete.
Initialization time for the task name: task 4 = 06 : 13 : 09
task 1 is complete.
Initialization time for the task name: task 5 = 06 : 13 : 09
task 3 is complete.
Time of execution for the task name: task 4 = 06 : 13 : 10
Time of execution for the task name: task 5 = 06 : 13 : 10
Time of execution for the task name: task 4 = 06 : 13 : 11
Time of execution for the task name: task 5 = 06 : 13 : 11
Time of execution for the task name: task 4 = 06 : 13 : 12
Time of execution for the task name: task 5 = 06 : 13 : 12
Time of execution for the task name: task 4 = 06 : 13 : 13
Time of execution for the task name: task 5 = 06 : 13 : 13
Time of execution for the task name: task 4 = 06 : 13 : 14
Time of execution for the task name: task 5 = 06 : 13 : 14
task 4 is complete.
task 5 is complete.
解释:从程序的输出可以明显看出,任务4和任务5只有在线程有空闲线程时才会执行。在此之前,额外的任务会被放入队列中。
从上面的示例中可以得出的结论是,当我们想执行50个任务但不想创建50个线程时,可以创建一个包含10个线程的线程池。因此,50个任务中的10个被分配执行,其余的任务被放入队列中。当10个线程中的任何一个线程变为空闲时,它会执行第11个任务。其他待处理的任务也会以同样的方式处理。
线程池中存在的风险
线程池中存在以下风险。
死锁:已知任何涉及多线程的程序都可能出现死锁,线程池引入了死锁的另一种情形。考虑这样一种情况,正在执行的所有线程都在等待队列中由于没有可用的线程而被阻塞,并等待来自被阻塞线程的结果。
线程泄漏:当从线程池中删除线程以执行任务时,线程未在任务完成后返回到线程池中,就会发生线程泄漏。例如,当线程抛出异常并且池类无法捕获此异常时,线程退出并减少了1个线程池的大小。如果同样的情况重复多次,那么线程池很可能变为空,因此池中没有可用于执行其他请求的线程。
资源争用:当线程池的大小非常大时,线程之间的上下文切换会浪费大量时间。当线程数超过最佳数量时,可能会导致饥饿问题,进而导致资源争用。
需要记住的要点
不要将等待其他任务结果的任务并发排队。这可能导致死锁情况,如上所述。
使用线程执行长时间运行的操作时必须小心。这可能导致线程永远等待并最终导致资源泄漏。
最后,必须显式结束线程池。如果不这样做,程序将继续执行,永远不会结束。在线程池上调用shutdown()方法来终止执行器。请注意,在关闭后尝试向执行器发送另一个任务将引发RejectedExecutionException。
为了降低JVM内存不足的可能性,可以控制JVM中可以运行的最大线程数。线程池在达到最大限制后无法创建新线程。
线程池可以使用已完成执行的线程。这样可以节省创建新线程所需的时间和资源。
调整线程池
线程池的准确大小取决于可用处理器的数量和线程需要执行的任务类型。如果系统具有P个处理器,这些处理器仅进行计算类型的处理,那么线程池的最大大小为P或P + 1可实现最大效率。然而,任务可能需要等待I/O,在这种情况下,必须考虑等待时间(W)与请求的服务时间(S)的比例;结果是最大效率的线程池大小为P *(1 + W / S)。
结论
线程池是组织应用程序的非常方便的工具,特别是在服务器端。从概念上讲,线程池很容易理解。然而,在处理线程池时,可能需要考虑许多问题。这是因为线程池存在一些风险(如上所述的风险)。