并发编程-多线程

本文介绍了在Java中通过继承Thread类、实现Runnable接口、Callable接口以及使用线程池来实现多线程的方法,强调了每种方式的特点、优势和适用场景,以及线程池的配置和使用注意事项。
摘要由CSDN通过智能技术生成

实现多线程其实有很多种方式,大体上可以分为以下四种:

一、继承Thread类

通过继承Thread类的方式可以实现多线程,在这里模拟一下通过继承Thread类的方式来实现多线程消费业务数据:

  1. 定义一个类,继承Thread,并实现消费逻辑
/**
 * 消费者
 */
@Data
@Slf4j
public class Consumer extends Thread {
    /**
     * 消费数据的过程
     */
    @Override
    public void run() {
        log.info(Thread.currentThread().getName() + "正在消费数据!!!");
        try {
            Thread.sleep(1000L);
        } catch (Exception e) {
            log.info(Thread.currentThread().getName() + "数据消费出错!!!");
        }
    }
}
  1. 使用进行多线程消费
/**
     * 消费数据
     */
@Override
public void comsumerData() {
    // 启动10个线程进行消费数据
    int maxThreadNum = 10;
    for (int i = 0; i < maxThreadNum; i++) {
        Consumer consumer = new Consumer();
        consumer.start();
    }
}

也可以使用自定义的线程池进行消费

/**
     * 自定义线程池
     */
@Autowired
private ThreadPoolExecutor threadPoolExecutor;

/**
     * 消费数据
     */
@Override
public void comsumerData() {
    // 启动10个线程进行消费数据
    int maxThreadNum = 10;
    for (int i = 0; i < maxThreadNum; i++) {
        Consumer consumer = new Consumer();
        threadPoolExecutor.execute(consumer);
    }
}

关于自定义线程池,定义方式以及参数构建可以参考《并发编程-线程池》.

二、实现Runnable接口

通过实现Runnable接口的方式,同样能够实现多线程.

  1. 定义一个类,实现Runnable接口,并且重写其run方法.
/**
 * 消费者
 */
@Data
@Slf4j
public class Consumer02 implements Runnable {

    /**
     * 消费数据的逻辑
     */
    @Override
    public void run() {
        log.info(Thread.currentThread().getName() + "正在消费数据!!!");
        try {
            Thread.sleep(1000L);
        } catch (Exception e) {
            log.info(Thread.currentThread().getName() + "数据消费出错!!!");
        }
    }

}
  1. 开启线程任务
Consumer02 threadTest01 = new Consumer02();
for (int i = 0; i < 10; i++) {
    new Thread(threadTest01).start();
}

上面这种方式其实是不建议使用的,无法控制线程的数量等,可能会带来较大的影响,推荐使用自定义线程池的方式创建。

  1. 业务层,利用自定义的线程池,开启多线程处理数据
/**
     * 自定义线程池
     */
    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;

    /**
     * 消费数据
     */
    @Override
    public void consumerData() {
        //模拟10次请求发送过来
        for (int i = 0; i < 10; i++) {
            //使用自定义的线程池对数据进行处理
            Consumer02 consumer02 = new Consumer02();
            threadPoolExecutor.execute(consumer02);
        }
    }

实现Runnable接口的方式与集成Thread的方式相比:

如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。

相比较之下,具有一定的优势,能够解决Java单继承所带来的缺陷.

三、实现Callable接口

对比,前两种方式创建线程的方式来说,这种方式的有点在于,它可以获取到线程任务执行后的返回值。

  1. 创建线程任务对象,并重写run
/**
 * @Author: yichangqiao
 * @Date: 2024/1/22 15:46
 */
@Data
@Slf4j
public class ThreadTest02 implements Callable<Integer> {

    /**
     * 业务逻辑代码
     *
     * @return
     * @throws Exception
     */
    @Override
    public Integer call() throws Exception {
        log.info(Thread.currentThread().getName() + "正在消费数据!!!");
        try {
            Thread.sleep(1000L);
            return 200;
        } catch (Exception e) {
            log.info(Thread.currentThread().getName() + "数据消费出错!!!");
        }
        return 500;
    }
}
  1. 使用该对象开启多线程执行
public static void testCallable() {
    // 创建线程任务对象
    ThreadTest02 threadTest02 = new ThreadTest02();
    // 利用futureTask开启线程任务
    FutureTask<Integer> integerFutureTask = new FutureTask<>(threadTest02);
    for (int i = 0; i < 10; i++) {
        // 开启线程执行任务
        new Thread(integerFutureTask).start();
        try {
            Integer result = integerFutureTask.get();
            log.info("线程{}执行结果:{}", i, result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

public static void main(String[] args) {
    testCallable();
}

这里的执行与其他两种方式的执行不同,实现Callable接口的线程任务,需要使用futureTask来进行初始化,并且使用futureTask来获取线程任务的返回结果。

注意:FutureTask的返回结果获取,是一个阻塞式的,如果长时间获取不到返回结果,线程一直不会释放,可能导致线程数越来越多,有两种解决方式:

  1. 指定获取超时时间,超过多长时间没获取到返回结果,就不再获取了。
Integer result = integerFutureTask.get(4,TimeUnit.SECONDS);
  1. 使用线程池。

四、线程池

线程池的创建需要结合实际的业务以及资源情况进行创建,这里只做一个简单的示例:

  1. 创建线程池
/**
 * 自定义线程池
 */
@Slf4j
@Configuration
public class ThreadPoolConfig {

    @Bean(name = "threadPoolExecutor")
    public ThreadPoolExecutor threadPoolExecutor() {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                //核心线程数量
                10,
                //最大线程数
                20,
                //临时线程最大存活时间
                10,
                //存活时间单位
                TimeUnit.SECONDS,
                //任务等待队列
                new ArrayBlockingQueue<>(20),
                //创建线程到的线程工厂
                Executors.defaultThreadFactory(),
                //拒绝策略
                new ThreadPoolExecutor.AbortPolicy());
        log.info("线程池初始化完成 ===========> {}", LocalDateTime.now());
        return executor;
    }

}
  1. 使用线程池
// 假设10个请求同时发过来
int maxThreadNum = 10;
for (int i = 0; i < maxThreadNum; i++) {
    // consumer实现多线程
    Consumer consumer = new Consumer();
    threadPoolExecutor.execute(consumer);
}

关于线程池的详细介绍和操作,可以看《并发编程-线程池》一文。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值