赞
踩
在Java中创建线程主要有以下几种方式:
示例代码:
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("通过继承Thread类的方式创建并运行线程");
}
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}
实现Runnable接口:
示例代码:
public class RunnableTask implements Runnable {
@Override
public void run() {
System.out.println("通过实现Runnable接口的方式创建并运行线程");
}
public static void main(String[] args) {
RunnableTask task = new RunnableTask();
Thread thread = new Thread(task);
thread.start();
}
}
使用Callable和Future接口(结合ExecutorService):
示例代码
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class CallableTask implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("通过Callable和Future创建并运行线程");
return "Task Result";
}
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newSingleThreadExecutor();
CallableTask task = new CallableTask();
Future<String> future = executor.submit(task);
// 获取线程执行的结果
String result = future.get();
executor.shutdown(); // 关闭线程池
System.out.println("线程执行结果:" + result);
}
}
示例代码:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(5); // 创建固定大小的线程池
for (int i = 0; i < 10; i++) {
Runnable worker = () -> {
System.out.println("线程:" + Thread.currentThread().getName() + "正在运行");
};
executor.execute(worker); // 提交任务到线程池
}
executor.shutdown(); // 关闭线程池,等待所有任务完成
}
}
以上是Java中创建和管理线程的主要方式,其中后两种方式更加强调了线程的复用和并发编程的高效性。
在Java中,创建线程池的推荐方式是直接实例化java.util.concurrent.ThreadPoolExecutor类,而不是使用Executors工具类提供的静态工厂方法。尽管Executors提供了方便快捷的线程池创建方法,但这些预设配置可能不适合所有场景,尤其是在资源管理、异常处理和性能优化方面。
创建线程池的方式: 通过自定义ThreadPoolExecutor参数来创建线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
workQueue,
threadFactory,
handler
);
七大构造方法参数详解:
corePoolSize
:核心线程数,即线程池中的常驻线程数。即使这些线程空闲,也不会被终止,除非调用了allowCoreThreadTimeOut(true)
方法或者线程池被关闭。maximumPoolSize
:线程池允许的最大线程数。当队列已满且有新任务提交时,如果当前线程数量小于最大线程数,则会创建新的线程来执行任务。keepAliveTime
:非核心线程闲置超时时长(单位由unit指定)。超过这个时间后,如果线程池中的线程数大于核心线程数,那么多余的线程将被终止。unit
:与keepAliveTime配套的时间单位,如TimeUnit.SECONDS、TimeUnit.MILLISECONDS等。workQueue
:工作队列,用于存储等待执行的任务。常见的队列实现有LinkedBlockingQueue
(无界链表队列)ArrayBlockingQueue
(有界数组队列)以及优先级队列PriorityBlockingQueue
等。threadFactory
:线程工厂,用于创建新线程。默认的线程工厂创建的新线程都是非守护线程,并且具有相同的NORM_PRIORITY优先级。可以自定义线程工厂为线程设置名称、优先级或添加其他行为。handler
:拒绝策略,在线程池和工作队列都满了的情况下,无法接受新任务时所采用的策略。内置四种拒绝策略:
AbortPolicy
:抛出RejectedExecutionException异常。CallerRunsPolicy
:调用者所在的线程自己执行该任务。DiscardPolicy
:默默丢弃任务,不抛出异常。DiscardOldestPolicy
:从工作队列中移除最早的任务(通常是最先阻塞的),然后尝试重新提交当前任务。通过自定义上述参数,可以根据应用程序的实际需求来精细调整线程池的行为,以达到最佳的并发性能和资源利用效率。同时,也可以避免因预设线程池参数不当而引发的问题,比如内存溢出(OOM)或系统响应延迟等。
不推荐直接使用Executors类的静态工厂方法创建线程池主要有以下几个原因:
缺乏灵活性和可定制性
: Executors提供的几个预设线程池,如newFixedThreadPool, newSingleThreadExecutor, 和 newCachedThreadPool等,虽然易于使用,但它们往往具有固定的配置,并且在某些场景下可能不是最优选择。例如,预设的线程池通常采用无界队列(如LinkedBlockingQueue),这意味着如果任务提交速度远大于处理速度,可能导致内存泄漏。潜在的OOM风险
:对于固定大小线程池(newFixedThreadPool)和单线程线程池(newSingleThreadExecutor),当工作队列设置为无界时,若大量任务堆积而无法及时消费,可能会耗尽系统内存。默认线程工厂限制
: 使用Executors创建的线程池,默认使用的是简单的线程工厂,生成的线程通常是非守护线程,没有自定义名称或优先级,这在一些对线程有特殊要求的应用场景中不够理想。资源管理和监控不便
: 预设的线程池对于线程生命周期管理、异常处理、饱和策略等方面的配置较为有限,不利于进行精细化管理和问题排查。安全性和健壮性考虑
: 在生产环境中,应确保线程池能够应对各种异常情况,例如合理地拒绝新任务、优雅地关闭线程池以及处理已提交任务的完成状态等。直接使用Executors创建的线程池可能不具备这些特性。因此,在实际应用中,建议自定义ThreadPoolExecutor
实例来创建线程池,这样可以更精确地控制线程池的大小、工作队列类型、饱和策略、线程工厂以及其他与线程池相关的参数,从而提高系统的稳定性和资源利用率。
ForkJoinPool是Java并发编程框架的一部分,它是Java 7引入的一个特殊的线程池实现,主要用于执行分治算法
(Divide-and-Conquer)任务。其基本原理主要基于以下几个核心概念:
工作窃取(Work Stealing)
: ForkJoinPool采用了工作窃取算法来提高并行处理的效率和CPU利用率。每个工作线程都有自己的双端队列来存放待处理的任务。当一个工作线程发现自己队列中的任务已经耗尽时,它会尝试从其他工作线程的队列尾部窃取任务来执行。这种机制确保了即使在非平衡任务分布情况下也能充分利用所有工作线程。Fork/Join模型
: 使用ForkJoinPool的任务通常继承自ForkJoinTask抽象类或者它的两个子类RecursiveAction和RecursiveTask。这些任务可以调用fork()方法将自己分割成更小的子任务,并递归地提交给线程池。当子任务完成后,通过调用join()方法合并结果。这样就可以将大任务分解为可独立计算的小任务,各个小任务并行计算后最终合并得到整个大任务的结果。任务调度与同步
: ForkJoinPool内部维护了一套高效的任务调度系统,能够有效地管理和调度大量的并行任务。同时,ForkJoinTask提供了必要的同步原语,确保任务执行过程中的数据一致性以及正确地完成结果的合并。动态调整线程数量
: ForkJoinPool默认根据处理器核心数创建相应数量的工作线程,并且可以根据运行时负载自动调整工作线程的数量,以适应系统资源的变化。使用场景:
大数据集的并行处理
:如对大量数据进行排序、搜索等操作时,可以通过ForkJoinPool进行高效的并行计算。图像处理
:在处理大规模图像运算或复杂图形渲染时,可以利用ForkJoinPool将复杂的像素运算拆分成多个子任务并行执行。科学计算和机器学习
:在处理大型矩阵运算、数值计算、或者某些机器学习算法时,可以利用ForkJoinPool的优势进行快速并行计算。树形或图遍历算法
:ForkJoinPool特别适用于那些具有天然分治性质的问题,例如深度优先搜索(DFS)、广度优先搜索(BFS)或其他形式的递归遍历。总之,ForkJoinPool适合于处理那些可以被自然划分为一系列相互独立的子任务的并行计算场景,尤其是当任务规模动态变化并且需要高效利用多核处理器资源时。ForkJoinPool是一种特殊的线程池,特别适合处理能够进行递归分解的任务。它采用了工作窃取
算法来提高并行效率,每个工作线程都有自己的双端队列,当一个工作线程的任务完成后,可以从其他线程的队列中窃取任务继续执行,从而充分利用CPU资源
。
在使用ForkJoinPool时,需要创建实现RecursiveAction
或RecursiveTask
接口的任务类,并正确地对任务进行拆解和合并
。同时,根据实际问题复杂度和硬件资源来调整线程池的大小
。
对于大量短生命周期的任务,可以考虑使用ThreadPoolExecutor
配合一个无界或者有适当容量的队列
(如SynchronousQueue或LinkedBlockingQueue),以减少线程创建和销毁开销。然而,需要注意的是,如果任务提交速度远大于处理速度,可能导致内存溢出。为防止这种情况,可以通过调整线程池大小
、采用具有饱和策略的队列
或其他自定义拒绝策略来控制任务积压。
检查任务是否过于细粒度
,导致线程大部分时间都在执行任务切换而非实际工作。锁竞争
或者其他同步机制引起的阻塞
现象,导致线程无法有效执行任务。确认工作队列是否合适
,如果是无界队列,可能存在任务积压但未被及时处理的情况。排查是否有外部资源限制
(如数据库连接池满、网络带宽瓶颈)影响了线程的执行速度。对整个系统进行全面性能分析
,包括CPU使用率、内存消耗、磁盘I/O等方面,找到潜在瓶颈并针对性地进行优化。CPU核心数
,通常将核心线程数设置为CPU核心数或其倍数可以充分利用硬件资源。任务类型和执行时间
,对于IO密集型任务,可以适当增大线程池大小,因为线程在等待IO操作时不会占用CPU;而对于CPU密集型任务,则应避免过度创建线程导致上下文切换频繁。系统负载情况和任务提交模式
,通过压力测试确定最佳线程池大小,同时观察内存使用、响应时间和吞吐量等性能指标。可调节的线程池大小
或者结合ThreadPoolExecutor的动态调整机制,在运行时根据负载自动调整线程数量。定位问题:
首先通过监控工具查看线程池的状态信息
,包括当前活跃线程数、阻塞队列长度、已完成任务数量等指标。分析是否存在任务堆积、线程资源是否充分利用等问题。另外,查看系统日志查找是否有RejectedExecutionException异常抛出,这可能表明拒绝策略正在起作用。
优化步骤:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。