网站首页 > 文章精选 正文
在中大型项目中,不可避免地需要执行异步任务,而异步任务的执行则是通过线程而执行的,因此掌握线程是如何创建的则是执行异步任务的第一步。
我们知道,常见的创建线程的方式有:
- 继承Thread类,重写run()方法
- 实现Runnable接口,重写run()方法
- 使用线程池创建线程
一、继承Thread类
public class ThreadOne extends Thread {
@Override
public void run() {
System.out.println("当前线程:" + Thread.currentThread().getName() + "执行了run方法");
}
public static void main(String[] args) {
System.out.println("当前线程:" + Thread.currentThread().getName());
new ThreadOne().start();
}
}
输出:
当前线程:main
当前线程:Thread-0执行了run方法
二、实现Runnable接口
public class ThreadTwo implements Runnable{
@Override
public void run() {
System.out.println("当前线程:" + Thread.currentThread().getName() + "执行了run方法");
}
}
public class Test {
public static void main(String[] args) {
System.out.println("当前线程:" + Thread.currentThread().getName());
new Thread(new ThreadTwo()).start();
}
}
输出:
当前线程:main
当前线程:Thread-0执行了run方法
三、使用线程池
为什么使用线程池创建线程? 因为线程的创建和销毁是非常消耗资源的,而线程池可以复用之前创建好的线程,因此减少了资源的消耗。
3.1 ThreadPoolExecutor
1. ThreadPoolExecutor类的构造方法
参数说明:
- corePoolSize: 常驻核心线程数,如果大于0,即使本地任务执行完也不会被销毁
- maximumPoolSize: 线程池能够容纳可同时执行的最大线程数
- keepAliveTime: 线程池中线程空闲的时间,当空闲时间达到该值时,线程会被销毁, 只剩下 corePoolSize 个线程数量。
- unit: 空闲时间的单位。一般以TimeUnit类定义时分秒。
- workQueue: 当请求的线程数大于 corePoolSize 时,线程进入该阻塞队列。
- threadFactory: 线程工厂,用来生产一组相同任务的线程,同时也可以通过它增加前缀名,虚拟机栈分析时更清晰
- handler: 执行拒绝策略,当 workQueue 已满,且超过maximumPoolSize 最大值,就要通过这个来处理,比如拒绝,丢弃等,这是一种限流的保护措施。
创建ThreadPoolFactory工厂类:
public final class ThreadPoolFactory {
private volatile static ThreadPoolExecutor executor;
private ThreadPoolFactory(){
}
/**
* 创建ExecutorService,单例模式(DCL)
* @return
*/
public static ExecutorService getExecutorService(){
if (executor == null){
synchronized (ThreadPoolFactory.class){
if (executor == null){
executor = new ThreadPoolExecutor(1, 5, 30, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(5),
new CustomThreadFactory("自定义线程"),
new ThreadPoolExecutor.AbortPolicy());
}
}
}
return executor;
}
/**
* 自定义线程工厂
*/
private static class CustomThreadFactory implements ThreadFactory{
/**
* 参数 代表是第1个ThreadPoolExecutor产生的
*/
private final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup threadGroup;
private final AtomicInteger threadNumber = new AtomicInteger(1);
public final String namePrefix;
CustomThreadFactory(String name) {
SecurityManager s = System.getSecurityManager();
threadGroup = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
if (null == name || "".equals(name.trim())) {
name = "pool";
}
namePrefix = name + "-" + poolNumber.getAndIncrement() + "-thread-";
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(threadGroup, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon()) {
t.setDaemon(false);
}
if (t.getPriority() != Thread.NORM_PRIORITY) {
t.setPriority(Thread.NORM_PRIORITY);
}
return t;
}
}
}
测试:
public class ThreadPoolFactoryTest {
public static void main(String[] args) {
ExecutorService service = ThreadPoolFactory.getExecutorService();
for (int i = 0; i < 50; i++) {
service.execute(() -> {
System.out.println("当前线程: " + Thread.currentThread().getName());
});
}
}
}
输出:由于线程数量过大,触发拒绝策略,抛出异常。
2. 阻塞队列BlockingQueue:
从构成关系图可以看出,阻塞队列的实现类主要有4种类型:
- LinkedBlockingQueue 无界队列,当不指定队列大小时,将会默认为Integer.MAX_VALUE大小的队列,因此大量的任务将会堆积在队列中,最终可能触发OOM。
- ArrayBlockingQueue 有界队列,基于数组的先进先出队列,此队列创建时必须指定大小。
- PriorityBlockingQueue 有界队列,基于优先级任务的,它是通过Comparator决定的。
- SynchronousQueue 这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务
3. 处理策略Handler:
- AbortPolicy 默认的拒绝策略,抛出RejectedExecutionException异常
- DiscardPolicy 相当大胆的策略,直接丢弃任务,没有任何异常抛出
- DiscardOldestPolicy 丢弃最老的任务,其实就是把最早进入工作队列的任务丢弃,然后把新任务加入到工作队列
- CallerRunsPolicy 提交任务的线程自己去执行该任务
4. 线程池的关闭
- shutdown() : 不会立刻终止线程,等所有缓存队列中的任务都执行完毕后才会终止。
- shutdownNow() : 立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
3.2 Executors类创建线程
Executors类创建线程池的方法归根结底都是调用ThreadPoolExecutor类,只不过对每个方法赋值不同的参数去构造ThreadPoolExecutor对象。
- newCachedThreadPool
newCachedThreadPool: 创建一个可缓存的线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
- newFixedThreadPool
newFixedThreadPool: 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
- newScheduledThreadPool
newScheduledThreadPool: 创建一个定长线程池,支持定时及周期性任务执行。
- newSingleThreadExecutor
newSingleThreadExecutor: 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
注意:线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式创建。
原因:因为上述四个方法的创建的队列大小默认都是Integer.MAX_VALUE,堆积过多的任务请求会可能导致OOM。
猜你喜欢
- 2024-12-27 Python启航:30天编程速成之旅(第23天)- 多线程从入门到精通
- 2024-12-27 10问10答:你真的了解线程池吗? 线程池的用法、原理
- 2024-12-27 线程池最佳线程数量到底要如何配置?
- 2024-12-27 线程池的创建方式,为什么阿里推荐自定义线程池?
- 2024-12-27 如何在C#中创建和使?线程池?请提供?例代码
- 2024-12-27 多线程编程 - 创建线程和结束线程
- 2024-12-27 Java面试篇基础部分-Java创建线程详解
- 2024-12-27 创建线程池有哪几种方式? 44.创建线程池有哪几种方式?
- 2024-12-27 创建线程池的4大方法,7个参数,4种拒绝策略
- 2024-12-27 进程、线程的创建和派生详细过程 线程进程定义
- 最近发表
- 标签列表
-
- newcoder (56)
- 字符串的长度是指 (45)
- drawcontours()参数说明 (60)
- unsignedshortint (59)
- postman并发请求 (47)
- python列表删除 (50)
- 左程云什么水平 (56)
- 计算机网络的拓扑结构是指() (45)
- 稳压管的稳压区是工作在什么区 (45)
- 编程题 (64)
- postgresql默认端口 (66)
- 数据库的概念模型独立于 (48)
- 产生系统死锁的原因可能是由于 (51)
- 数据库中只存放视图的 (62)
- 在vi中退出不保存的命令是 (53)
- 哪个命令可以将普通用户转换成超级用户 (49)
- noscript标签的作用 (48)
- 联合利华网申 (49)
- swagger和postman (46)
- 结构化程序设计主要强调 (53)
- 172.1 (57)
- apipostwebsocket (47)
- 唯品会后台 (61)
- 简历助手 (56)
- offshow (61)