网站首页 > java教程 正文
在日常开发过程中,为了提高程序的运行效率,往往会采用使用多线程。Java实现多线程主要有以下几种方式:
- 继承Thread类: 创建一个新的类继承自Thread类,并重写其run()方法,在run()方法中定义需要并行执行的代码。然后创建这个自定义类的实例,并调用其start()方法来启动线程。这种方式的缺点是Java不支持多重继承,因此如果自定义类已经继承了其他类,则不能采用此方式。
- 实现Runnable接口: 创建一个类实现Runnable接口,并实现其run()方法。然后将此类的实例作为参数传递给Thread类的构造器,创建Thread对象,并通过调用该对象的start()方法来启动线程。这种方式因为基于接口实现,所以更加灵活,允许类继承其他类。
- 实现Callable接口和使用FutureTask: 与Runnable类似,但Callable接口提供了一个call()方法,该方法可以有返回值并且可以抛出异常。要将Callable任务转换为线程,需要将其包装进一个FutureTask对象,然后将FutureTask作为参数传递给Thread对象,或者直接提交给ExecutorService来管理执行。FutureTask还提供了检查任务执行状态和获取返回值的方法。
- 使用线程池(ExecutorService): 通过Executors类提供的工厂方法创建不同类型的线程池,如固定大小的线程池newFixedThreadPool、单线程的线程池newSingleThreadExecutor、可缓存线程池newCachedThreadPool等。线程池可以管理线程的生命周期,提高线程复用率,控制最大并发数,有效提高程序性能。提交任务到线程池使用execute(Runnable)或submit(Runnable/Callable)方法。
下面聊下在工作中使用线程池遇到的一个问题,项目中有一个线程工具类,大概如下:
public class ThreadUtil {
/**
* 核心线程数
*/
private static final int corePoolSize = 5;
/**
* 最大线程数
*/
private static final int maximumPoolSize = 10;
/**
* 线程存活时间
*/
private static final int keepAliveTime = 2000;
private static final ThreadPoolExecutor pool;
static {
pool = new ThreadPoolExecutor(corePoolSize,
maximumPoolSize,
keepAliveTime,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(200),
Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
}
public static ThreadPoolExecutor getPool(){
return pool;
}
}
该类在初始化时会创建一下线程池,并提供静态方法返回线程池。本人知道有这么一个类存在,但是一直没有使用过。最近一个同事在使用这个类时遇到了一个问题,大概代码如下:
public void test02(){
CountDownLatch countDownLatch = new CountDownLatch(50);
ThreadPoolExecutor pool = ThreadUtil.getPool();
for (int i = 0; i < 5; i++) {
pool.submit(()->{
try {
//模拟操作,等待5s
Thread.sleep(5000L);
} catch (InterruptedException e) {
log.error("线程中断", e);
}
countDownLatch.countDown();
});
}
try {
boolean await = countDownLatch.await(30L, TimeUnit.SECONDS);
if(!await){
log.error("任务执行超时");
}
} catch (InterruptedException e) {
log.error("线程中断", e);
}
}
该方法在执行时,有时成功,有时却提示“任务执行超时”。我看下了代码比较简单,没有明显的问题。思考了一会,线程里的方法并不复杂,不会执行耗时不会很长。那么问题应该出现在线程池本身上,这里线程池使用的公共方法获取的,那么应该是同时有其他任务用到了线程池,导致新的任务还没来的及超时。使用开发工具看了下,确实有好几处代码也使用了该线程池。为了验证问题,对代码临时修改下,使用java.util.concurrent.Executors#newFixedThreadPool(int):
public void test02(){
CountDownLatch countDownLatch = new CountDownLatch(50);
ExecutorService pool = Executors.newFixedThreadPool(5);
for (int i = 0; i < 5; i++) {
pool.submit(()->{
try {
//模拟操作,等待5s
Thread.sleep(5000L);
} catch (InterruptedException e) {
log.error("线程中断", e);
}
countDownLatch.countDown();
});
}
pool.shutdown();
try {
boolean await = countDownLatch.await(30L, TimeUnit.SECONDS);
if(!await){
log.error("任务执行超时");
}
} catch (InterruptedException e) {
log.error("线程中断", e);
}
}
经过测试,问题不再复现,得到解决。在此大概总结下线程池知识进行简单总结。
Java线程池的流程主要涉及以下几个步骤,这些步骤帮助管理线程的创建、执行任务、复用和销毁,从而提高程序的效率和响应速度。以下是线程池工作的大致流程:
- 创建线程池:通过Executors类的静态方法或直接使用ThreadPoolExecutor构造器来创建线程池。创建时可以指定线程池的核心参数,如核心线程数(corePoolSize)、最大线程数(maximumPoolSize)、线程空闲时间(keepAliveTime)、任务队列(BlockingQueue<Runnable>)等。
- 提交任务:当有新任务需要执行时,通过线程池的execute(Runnable)或submit(Callable<T>)方法提交任务。execute()用于提交不需要返回值的任务,而submit()则用于提交需要返回值的任务,并且可以获取Future对象来监控任务状态和结果。
- 任务调度:
- 如果当前运行的线程少于核心线程数,新提交的任务会被立即启动一个新的线程来执行。
- 如果当前运行的线程数等于核心线程数且任务队列未满,任务会被放入任务队列中等待执行。
- 如果当前运行的线程数等于核心线程数且任务队列已满,且线程数小于最大线程数,会创建新的线程来处理任务。
- 如果当前线程数已经达到最大线程数并且队列也满了,根据策略(如AbortPolicy, CallerRunsPolicy, DiscardPolicy, DiscardOldestPolicy等)处理超出部分的任务,默认情况下会抛出RejectedExecutionException异常。
- 线程复用:线程池中的线程在执行完一个任务后不会立即销毁,而是会继续从任务队列中取出下一个任务来执行,直到队列为空。如果设置了线程空闲时间,那么在空闲时间到达后,超过核心线程数的空闲线程会被终止以减少资源占用。
- 关闭线程池:可以通过调用线程池的shutdown()或shutdownNow()方法来关闭线程池。shutdown()会等待所有已提交的任务完成后再关闭线程池,而shutdownNow()会尝试中断所有正在执行的任务并关闭线程池。
猜你喜欢
- 2024-10-29 推荐一款码云GVP级别Star近1W的开源Java后台管理系统:RuoYi
- 2024-10-29 Android Studio编写运行测试纯java代码可带main()函数
- 2024-10-29 Java官方笔记1编写运行Java程序(java运行环境下载)
- 2024-10-29 宝藏脚本!Windows快速启动Java应用服务,提升开发效率!
- 2024-10-29 JVM是如何运行Java代码的(jvm怎么运行的)
- 2024-10-29 JAVA线上问题排查利器-Arthas(java在线答疑系统)
- 2024-10-29 用java代码启动一个电脑上的应用程序
- 2024-10-29 远程调试Java程序(java控制远程设备)
- 2024-10-29 BeanShell:动态执行java代码(java动态运行一段代码)
- 2024-10-29 Java线上问题排查神器Arthas实战分析
你 发表评论:
欢迎- 07-15采用Oracle OSB总线进行服务注册和接入
- 07-15javaEE 新闻管理系统 oracle11+tomcat6
- 07-15从Oracle演进看数据库技术的发展(oracle数据库发展史)
- 07-15如何升级oracle数据库安全补丁(oraclepsu补丁升级)
- 07-15【权威发布】关于Oracle WebLogic Server未授权远程代码执行高危漏洞的预警通报
- 07-15【mykit-data】 数据库同步工具(数据库表同步工具)
- 07-15[Java速成] 数据库基础,Connector/J、JDBC、JPA的关系(day 7)
- 07-15Google前工程主管“入住”Oracle(google浏览器找不到以前的书签)
- 最近发表
-
- 采用Oracle OSB总线进行服务注册和接入
- javaEE 新闻管理系统 oracle11+tomcat6
- 从Oracle演进看数据库技术的发展(oracle数据库发展史)
- 如何升级oracle数据库安全补丁(oraclepsu补丁升级)
- 【权威发布】关于Oracle WebLogic Server未授权远程代码执行高危漏洞的预警通报
- 【mykit-data】 数据库同步工具(数据库表同步工具)
- [Java速成] 数据库基础,Connector/J、JDBC、JPA的关系(day 7)
- Google前工程主管“入住”Oracle(google浏览器找不到以前的书签)
- Oracle数据库云服务系列新增前所未有的企业级功能
- 直播预告丨如何实现Oracle存储过程到java的一键转化
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)