java线程池实例_Java线程池 - ThreadPoolExecutor示例

为什么需要Java中的线程池?答案通常是,当您在Java中开发一个简单的并发应用程序时,您创建一些Runnable的对象,然后创建相应的线程对象来执行它们。在Java中创建线程是一项昂贵的操作。如果每次执行任务时都开始创建新的线程实例,那么应用程序的性能肯定会下降。

1. 线程池在java中是如何工作的

线程池是预初始化线程的集合。通常集合的大小是固定的,但不是强制的。它有助于使用相同线程执行N个任务。如果线程任务比线程多,那么任务需要在类似队列的结构中等待(FIFO -先进先出)。

当任何线程完成它的执行时,它可以从队列中获取一个新任务并执行它。当所有任务完成时,线程保持活动状态并等待线程池中的更多任务。

2ecd87db3902

Thread Pool

监视程序一直监视队列(通常是阻塞队列BlockingQueue)以执行任何新任务。一旦任务出现,线程就会再次开始接收并执行任务。

2. ThreadPoolExecutor

自Java 5以来,Java并发API提供了一个机制执行器框架。类似的接口为Executor接口、其子接口ExecutorService和实现这两个接口的ThreadPoolExecutor类。

ThreadPoolExecutor分离任务的创建和执行。使用ThreadPoolExecutor,您只需要实现可运行对象并将它们发送到执行程序。它负责它们的执行、实例化和使用必要的线程运行。

不仅如此,使用线程池还提高了性能。当您将任务发送给执行程序时,它会尝试使用一个线程池的线程来执行此任务,以避免线程的连续生成。

3.如何创建ThreadPoolExecutor

我们可以使用java.util.concurrent.Executors接口中的预先构建的方法创建以下5种类型的线程池执行器。

固定线程池执行程序——创建一个线程池,该线程池重用固定数量的线程来执行任意数量的任务。如果在所有线程都处于活动状态时提交了其他任务,它们将在队列中等待,直到线程可用。它最适合在现实生活中使用。

ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(10);

缓存的线程池执行程序——创建一个线程池,该线程池根据需要创建新线程,但在可用时将重用以前构造的线程。如果任务长时间运行,则不要使用此线程池。如果线程的数量超出了系统所能处理的范围,则会导致系统崩溃。

ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newCachedThreadPool();

调度线程池执行程序——创建一个线程池,该线程池可以调度命令在给定延迟后运行或定期执行。

ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newScheduledThreadPool(10);

单线程池执行程序-创建单线程来执行所有任务。当您只有一个任务要执行时就可以使用它。

ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newSingleThreadExecutor();

工作窃取线程池执行程序-创建一个线程池,该线程池维护足够的线程来支持给定的并行级别。这里的并行级别是指在多处理器机器上,在单点时间内执行给定任务所使用的线程的最大数量。

ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newWorkStealingPool(4);

4. ThreadPoolExecutor示例

4.1. 创建任务

让我们创建一个任务,每次都需要随机时间来完成它。

public class Task implements Runnable {

private String name;

public Task(String name) {

this.name = name;

}

public String getName() {

return name;

}

public void run() {

try {

Long duration = (long) (Math.random() * 10);

System.out.println("Executing : " + name);

TimeUnit.SECONDS.sleep(duration);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

4.2. 使用线程池执行器执行任务

给定的程序创建5个任务并提交到executor队列。Executor使用两个线程执行所有任务。

public class ThreadPoolExample {

public static void main(String[] args) {

ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(2);

for (int i = 1; i <= 5; i++) {

Task task = new Task("Task " + i);

System.out.println("Created : " + task.getName());

executor.execute(task);

}

executor.shutdown();

}

}

Program output:

Created : Task 1

Created : Task 2

Created : Task 3

Created : Task 4

Created : Task 5

Executing : Task 1

Executing : Task 2

Executing : Task 3

Executing : Task 4

Executing : Task 5

5. ScheduledThreadPoolExecutor

当您必须只执行一次惟一任务时,固定线程池或缓存线程池是很好的选择。当您需要执行一个任务,重复N次,或者N次固定次数,或者在固定延迟之后无限次,您应该使用ScheduledThreadPoolExecutor。

ScheduledThreadPoolExecutor提供了4种方法,它们提供不同的功能以重复的方式执行任务。

ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit)——创建并执行在给定延迟之后启用的任务。

ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit)——创建并执行在给定延迟之后启用的ScheduledFuture。

ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long delay, TimeUnit unit)——创建并执行一个周期性动作,该动作在给定的初始延迟之后首先启用,然后在给定的延迟期间启用。如果此任务的任何执行花费的时间超过其周期,则后续执行可能会延迟开始,但不会并发执行。

ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)——创建并执行一个周期性动作,该动作在给定的初始延迟之后首先启用,然后在给定的延迟期间启用。无论一个长时间运行的任务花费多少时间,两次执行之间都会有一个固定的延迟时间间隔。

5.1. ScheduledThreadPoolExecutor示例

public class ScheduledThreadPoolExecutorExample {

public static void main(String[] args) {

ScheduledThreadPoolExecutor executor = (ScheduledThreadPoolExecutor) Executors.newScheduledThreadPool(2);

Task task = new Task("Repeat Task");

System.out.println("Created : " + task.getName());

executor.scheduleWithFixedDelay(task, 2, 2, TimeUnit.SECONDS);

}

}

public class Task implements Runnable {

private String name;

public Task(String name) {

this.name = name;

}

public String getName() {

return name;

}

public void run() {

try {

Long duration = (long) (Math.random() * 10);

System.out.println("Executing : " + name + ", Current Seconds : " + new Date());

TimeUnit.SECONDS.sleep(duration);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

输出结果为:

Created : Repeat Task

Executing : Repeat Task, Current Seconds : Mon Mar 04 16:07:05 CST 2019

Executing : Repeat Task, Current Seconds : Mon Mar 04 16:07:13 CST 2019

Executing : Repeat Task, Current Seconds : Mon Mar 04 16:07:17 CST 2019

Executing : Repeat Task, Current Seconds : Mon Mar 04 16:07:22 CST 2019

Executing : Repeat Task, Current Seconds : Mon Mar 04 16:07:27 CST 2019

6. java中的自定义线程池实现

尽管Java通过Executor框架具有非常健壮的线程池功能。如果没有executor,就不应该创建自己的自定义线程池。我强烈反对这种企图。然而,如果您想创建它来学习,下面给出的是Java中的线程池实现。

@SuppressWarnings("unused")

public class CustomThreadPool {

// 线程池大小

private final int poolSize;

// 线程池内部是一个数组

private final WorkerThread[] workers;

// 先进先出队列

private final LinkedBlockingQueue queue;

public CustomThreadPool(int poolSize) {

this.poolSize = poolSize;

queue = new LinkedBlockingQueue();

workers = new WorkerThread[poolSize];

for (int i = 0; i < poolSize; i++) {

workers[i] = new WorkerThread();

workers[i].start();

}

}

public void execute(Runnable task) {

synchronized (queue) {

queue.add(task);

queue.notify();

}

}

private class WorkerThread 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();

}

try {

task.run();

} catch (RuntimeException e) {

System.out.println("Thread pool is interrupted due to an issue: " + e.getMessage());

}

}

}

}

public void shutdown() {

System.out.println("Shutting down thread pool");

for (int i = 0; i < poolSize; i++) {

workers[i] = null;

}

}

}

我们使用ThreadPoolExecutor执行同样的任务

public class CustomThreadPoolExample {

public static void main(String[] args) {

CustomThreadPool customThreadPool = new CustomThreadPool(2);

for (int i = 1; i <= 5; i++) {

Task task = new Task("Task " + i);

System.out.println("Created : " + task.getName());

customThreadPool.execute(task);

}

}

}

程序输出为:

Created : Task 1

Created : Task 2

Created : Task 3

Created : Task 4

Created : Task 5

Executing : Task 1, Current Seconds : Mon Mar 04 16:24:53 CST 2019

Executing : Task 2, Current Seconds : Mon Mar 04 16:24:53 CST 2019

Executing : Task 3, Current Seconds : Mon Mar 04 16:24:58 CST 2019

Executing : Task 4, Current Seconds : Mon Mar 04 16:25:00 CST 2019

Executing : Task 5, Current Seconds : Mon Mar 04 16:25:01 CST 2019

上面是非常原始的线程池实现,有很多改进。但是,与其完善上面的代码,不如专注于学习Java executor框架。

还要注意,不正确的池或队列处理也会导致死锁或资源抖动。Java社区对Executor框架进行了良好的测试,您当然可以避免这些问题。

7. 总结

ThreadPoolExecutor类有四个不同的构造函数,但是由于它们的复杂性,Java并发API提供了Executors类来构造executor和其他相关对象。尽管我们可以直接使用其构造函数之一创建ThreadPoolExecutor,但建议使用Executors类。

我们在上面创建的缓存线程池,如果需要执行新任务,它会创建新线程;如果已经完成了正在运行的任务(这些任务现在是可用的)的执行,它会重用现有的线程。但是,缓存的线程池有一个缺点,即新任务的线程总是处于不稳定状态,因此,如果您向这个执行程序发送太多的任务,您可能会使系统过载。这可以通过使用固定线程池来解决,我们将在下一教程中学习这个问题。

ThreadPoolExecutor类和一般的执行器的一个关键方面是,必须显式地结束它。如果不这样做,执行程序将继续执行,程序将不会结束。如果执行程序没有要执行的任务,它将继续等待新任务,并且不会结束执行。Java应用程序在其所有非守护进程线程完成执行之前不会结束,因此,如果不终止执行程序,应用程序将永远不会结束。

要向执行程序表明想要完成它,可以使用ThreadPoolExecutor类的shutdown()方法。当执行程序完成所有挂起任务的执行时,它就完成了执行。在您调用shutdown()方法之后,如果您尝试将另一个任务发送给执行程序,它将被拒绝,执行程序将抛出RejectedExecutionException异常。

ThreadPoolExecutor类提供了许多方法来获取关于其状态的信息。在示例中,我们使用getPoolSize()、getActiveCount()和getCompletedTaskCount()方法来获取关于池大小、线程数和执行程序完成的任务数的信息。您还可以使用getLargestPoolSize()方法,该方法每次返回池中已经存在的最大线程数。

ThreadPoolExecutor类还提供了与执行器的终结相关的其他方法。这些方法有:

shutdownNow():该方法立即关闭执行程序。它不执行挂起的任务。它返回一个包含所有这些挂起任务的列表。调用此方法时正在运行的任务将继续执行,但该方法不会等待它们的结束。

isTerminated():如果您调用了shutdown()或shutdownNow()方法,并且执行程序完成了关闭该方法的过程,则该方法返回true。

isShutdown():如果调用了executor的shutdown()方法,则该方法返回true。

awaitTermination(long timeout,TimeUnitunit):该方法阻塞调用线程,直到执行程序的任务结束或超时发生。TimeUnit类是一个枚举,它具有以下常量:DAYS,HOURS,MICROSECONDS等。

参考资料

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java 语言中,可以使用 `ThreadPoolExecutor` 类来创建线程池实例。以下是一个使用 `ThreadPoolExecutor` 创建线程池示例代码: ```java import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class ThreadPoolExample { public static void main(String[] args) { // 创建 ThreadPoolExecutor 实例 ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(5); // 可选:设置线程池参数 executor.setCorePoolSize(5); // 核心线程数 executor.setMaximumPoolSize(10); // 最大线程数 executor.setKeepAliveTime(1, TimeUnit.MINUTES); // 线程空闲时间 executor.setQueueCapacity(100); // 队列容量 // 提交任务给线程池执行 for (int i = 0; i < 10; i++) { Runnable task = new MyTask(i); executor.execute(task); } // 关闭线程池 executor.shutdown(); } static class MyTask implements Runnable { private int taskId; public MyTask(int taskId) { this.taskId = taskId; } @Override public void run() { System.out.println("Task " + taskId + " is executing."); // 任务逻辑代码... } } } ``` 在上述示例中,首先通过 `Executors.newFixedThreadPool()` 方法创建一个固定大小的线程池,然后可以根据需要使用 `setXXX()` 方法设置线程池的参数。接下来,通过调用 `execute()` 方法提交任务给线程池执行。最后,通过调用 `shutdown()` 方法关闭线程池。 注意:在实际开发中,根据具体需求,可能需要根据任务类型和系统资源情况来调整线程池的参数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值