线程以及线程池的学习与使用

本文详细介绍了Java中的线程基础知识,包括线程的五种状态、三种创建方式(继承Thread、实现Runnable、实现Callable)以及示例代码。接着深入探讨了线程池的概念,解释了为何要使用线程池以提高资源利用率,并展示了ThreadPoolExecutor的创建及参数解析,还提到了四种拒绝策略。最后,文章阐述了SpringBoot中如何配置和使用线程池,以及在业务中如何实现异步处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 线程的基本知识

        线程是我们工作中必然会接触到的一个概念,线程是程序执行的最小单元。线程的状态有五个,分别是新建、就绪、运行、阻塞、死亡。各个状态之间的流转关系如下

线程的创建有三种方式,分别是:

  1. 继承Thread类
  2. 实现Runnable接口
  3. 实现Callable接口

三种实现方式中,第一种实现的本质也是实现了Runnable接口,简单演示一下三种创建线程的方式。

1.1 继承Thread类

        创建的方式就是继承Thread类,重写run方法,详情如下

package com.study.rabbitmq.test;

/**
 * @Author alen
 * @DATE 2022/6/19 8:49
 */
public class MyThread extends Thread {

    @Override
    public void run() {
        System.out.println("hello...");
    }

    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        t1.start();
    }
}

使用start()方法来启动线程,让线程进入就绪状态,等待CPU资源,然后执行。

1.2 实现Runnable接口

        创建方式是实现Runnable接口,重写run方法,详情代码如下

package com.study.rabbitmq.test;

/**
 * @Author alen
 * @DATE 2022/6/19 8:58
 */
public class MyRunnableTest implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + ":hello....");
    }

    public static void main(String[] args) {
        MyRunnableTest t1 = new MyRunnableTest();
        new Thread(t1, "自定义线程名称").start();
    }
}

1.3 实现Callable接口

        实现Callable接口,重写call方法,详情如下

package com.study.rabbitmq.test;

import java.util.concurrent.*;

/**
 * @Author alen
 * @DATE 2022/6/19 9:04
 */
public class MyCallableTest implements Callable<Boolean> {

    @Override
    public Boolean call() throws Exception {
        System.out.println("信息:hello...");
        return true;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCallableTest t1 = new MyCallableTest();
        //线程池的创建
        ExecutorService executor = Executors.newFixedThreadPool(1);
        //提交执行
        Future<Boolean> submit = executor.submit(t1);
        //获取执行结果
        Boolean flag = submit.get();
        System.out.println("flag:" + flag);
    }
}

这种方式的使用,本质上是线程池的使用。线程池的使用在我们工作中也比较常见,典型的就是各类数据库的连接都是使用了线程池。线程池可以理解为是线程提前创建好,放在一个次子里面,要用的时候,就把线程拿出来使用。

2. 线程池ThreadPoolExecutor

        由于线程的创建是比较消耗资源的,在高并发的请求下,频繁的创建和销毁线程对于资源来说是非常浪费的。也会极大的降低资源的处理能力,所以需要使用线程池。线程创建后,放入线程池。然后线程不断复用,达到提升处理效率。

2.1 线程池ThreadPoolExecutor的创建

        在学习线程池ThreadPoolExecutor时,对于其几个核心的参数一定需要先了解清楚。参数的解释如下,ThreadPoolExecutor的构造函数源码如下:

/**
 * ThreadPoolExecutor构造函数
 *
 * @param corePoolSize 核心线程数。默认情况下,核心线程会一直存活,但将allowCoreThreadTimeout设置为true时,核心线程也会超时回收
 * @param maximumPoolSize 线程池所能容纳的最大线程数。当活跃线程数达到该数值后,后续的新任务将会阻塞
 * @param keepAliveTime 线程闲置超时时长。如果超过该时长,非核心线程就会被回收
 * @param unit 指定keepAliveTime参数的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)
 * @param workQueue 任务队列,当线程处理不过来时,任务暂时存放队列中
 * @param threadFactory 线程工厂。用于指定为线程池创建新线程的方式
 * @param handler 拒绝策略。当达到最大线程数时需要执行的饱和策略
 */
public ThreadPoolExecutor(int corePoolSize,
						  int maximumPoolSize,
						  long keepAliveTime,
						  TimeUnit unit,
						  BlockingQueue<Runnable> workQueue,
						  ThreadFactory threadFactory,
						  RejectedExecutionHandler handler) {
	if (corePoolSize < 0 ||
			maximumPoolSize <= 0 ||
			maximumPoolSize < corePoolSize ||
			keepAliveTime < 0)
		throw new IllegalArgumentException();
	if (workQueue == null || threadFactory == null || handler == null)
		throw new NullPointerException();
	this.acc = System.getSecurityManager() == null ?
			null :
			AccessController.getContext();
	this.corePoolSize = corePoolSize;
	this.maximumPoolSize = maximumPoolSize;
	this.workQueue = workQueue;
	this.keepAliveTime = unit.toNanos(keepAliveTime);
	this.threadFactory = threadFactory;
	this.handler = handler;
}

其中拒绝策略有很多种,具体如下

//丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.AbortPolicy;
//也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardPolicy;
//丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.DiscardOldestPolicy;
//由调用线程处理该任务
ThreadPoolExecutor.CallerRunsPolicy;

使用代码如下

package com.study.rabbitmq.test.pool;

import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @Author alen
 * @DATE 2022/6/19 9:27
 */
public class ThreadPoolExecutorTest {

    //核心线程数
    private static final Integer CORE_POOL_SIZE = 20;
    //最大线程数
    private static final Integer MAXIMUM_POOL_SIZE = 20;
    //线程空闲超时时长
    private static final Long KEEP_ALIVE_TIME = 120L;

    public static void main(String[] args) {
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
                                                                MAXIMUM_POOL_SIZE,
                                                                KEEP_ALIVE_TIME,
                                                                TimeUnit.HOURS,
                                                                new LinkedBlockingQueue<>(100),
                                                                Executors.defaultThreadFactory(),
                                                                new ThreadPoolExecutor.AbortPolicy());

        for (int i = 0; i < 100; i++) {
            poolExecutor.execute(() -> {
                System.out.println(Thread.currentThread().getName() + ":hello.....");
            });
        }
    }
}

线程池中有五种常见的创建方式,分别是

  1. Executors.newCachedThreadPool():可缓存线程池
  2. Executors.newFixedThreadPool(10):固定大小的线程池
  3. Executors.newScheduledThreadPool(2):任务调度的线程池
  4. Executors.newSingleThreadExecutor():单线程的线程池
  5. Executors.newWorkStealingPool():足够大小的线程池

这些线程池的创建底层都是通过创建ThreadPoolExecutor对象,不同类型的线程池的参数不同而已。

3. 线程池的原理

        线程池ThreadPoolExecutor对象中最核心的方法execute(),所以我们得理解这个方法的逻辑。源码如下

public void execute(Runnable command) {
	if (command == null)
		throw new NullPointerException();
	
	/*
	*分三步进行:
	*
	* 1. 如果运行的线程少于corePoolSize,请尝试
	* 以给定命令作为第一个线程启动新线程
	* 任务。对addWorker的调用以原子方式检查运行状态和
	* workerCount,从而防止错误警报
	* 在不应该的情况下,通过返回false来执行线程。
	*
	* 2. 如果任务可以成功排队,那么我们仍然需要
	* 再次检查是否应该添加线程
	*(因为自上次检查以来,现有的已死亡)或
	* 自进入此方法以来,池已关闭。所以我们
	* 重新检查状态,如有必要,在以下情况下回滚排队
	* 已停止,如果没有线程,则启动新线程。
	* 3. 如果无法将任务排队,则尝试添加新的
	* 螺纹。如果失败了,我们知道我们已经被关闭或饱和了
	* 所以拒绝这个任务。
	*/
	int c = ctl.get();
	if (workerCountOf(c) < corePoolSize) {
		if (addWorker(command, true))
			return;
		c = ctl.get();
	}
	if (isRunning(c) && workQueue.offer(command)) {
		int recheck = ctl.get();
		if (! isRunning(recheck) && remove(command))
			reject(command);
		else if (workerCountOf(recheck) == 0)
			addWorker(null, false);
	}
	else if (!addWorker(command, false))
		reject(command);
}

更详细的源码分析,后续使用新的博客来分析。

4. Spring Boot中使用多线程

4.1 添加配置类

        新建配置类,在配置类中配置线程池的相关配置,配置详情如下

package com.test.executor.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.ThreadPoolExecutor;

/**
 * @Author alen
 * @DATE 2022/3/8 13:48
 * 线程池配置类
 */
@Configuration
//开启线程池
@EnableAsync
public class TaskExecutorConfig {

    @Bean(name = "threadPoolTaskExecutor")
    public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心线程数
        executor.setCorePoolSize(10);
        // 最大线程数
        executor.setMaxPoolSize(20);
        // 队列大小
        executor.setQueueCapacity(100);
        // 线程空闲超时时间
        executor.setKeepAliveSeconds(30);
        // 是否允许超时 true:允许 false:不允许
        executor.setAllowCoreThreadTimeOut(true);
        // 线程名字前缀
        executor.setThreadNamePrefix("thread-task-pool-");
        // 拒绝策列 AbortPolicy:队列满后新进的任务直接拒绝并抛出异常
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        // 设置关闭线程池时,需要等待线程执行完成后方可关闭
        executor.setWaitForTasksToCompleteOnShutdown(true);
        // 初始化
        executor.initialize();
        return executor;
    }
}

4.2 线程池业务中使用

        创建一个业务处理类,类中的业务处理方法标识为异步并且指定使用的线程池,详情如下

package com.test.executor.service.impl;

import com.test.executor.service.ExecutorService;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

/**
 * @Author alen
 * @DATE 2022/3/8 14:44
 */
@Service
public class ExecutorServiceImpl implements ExecutorService {

    /**
     * 此逻辑将使用线程池处理
     */
    @Async("threadPoolTaskExecutor")
    public void seat() {
        try {
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + "-----线程池开始处理任务");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

业务处理在逻辑中的使用如下

package com.test.executor.controller;

import com.test.executor.service.ExecutorService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author alen
 * @DATE 2022/3/8 14:29
 */
@RestController
@RequestMapping("/test")
public class ExecutorController {

    @Autowired
    private ExecutorService executorService;

    @GetMapping("/seat")
    public String test() {
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10; i++) {
            executorService.seat();
        }
        long end = System.currentTimeMillis();
        return "任务执行中。。。" + (end - start) + "ms";
    }
}

可以自行测试一下。线程池的基础知识就先了解到这。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值