Java实现多线程方式详解

一、简介

  多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能,我们今天就来聊聊我们工作中实现多线程的基本方式,本文中Spring Boot 版本为2.5.2,JDK环境为 1.8,本文中使用到的依赖如下:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
        <version>2.5.2</version>
    </dependency>
    
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.16.14</version>
    </dependency>

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>compile</scope>
    </dependency>

二、继承Thread类

继承Thread类,然后重写run()方法即可

package com.alian.csdn.thread;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class MyThread extends Thread {

    private String threadName;

    public MyThread(String threadName) {
        this.threadName = threadName;
    }

    @Override
    public void run() {
        log.info("继承Thread类,子线程【{}】执行,优先级【{}】", this.threadName, Thread.currentThread().getPriority());
    }

}

我们写个测试方法

package com.alian.csdn.thread;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

@Slf4j
public class MyThreadTest {

    @Test
    public void testMyThread() {
        for (int i = 0; i < 3; i++) {
            MyThread myThread = new MyThread("MyThread-" + i);
            myThread.start();
        }
        log.info("继承Thread类,主线程【{}】执行,优先级【{}】", Thread.currentThread().getName(), Thread.currentThread().getPriority());
    }

}

运行结果:

19:51:08.249 [Thread-1] INFO com.alian.csdn.thread.MyThread - 继承Thread类,子线程【MyThread-1】执行,优先级【5】
19:51:08.249 [main] INFO com.alian.csdn.thread.MyThreadTest - 继承Thread类,主线程【main】执行,优先级【5】
19:51:08.249 [Thread-0] INFO com.alian.csdn.thread.MyThread - 继承Thread类,子线程【MyThread-0】执行,优先级【5】
19:51:08.249 [Thread-2] INFO com.alian.csdn.thread.MyThread - 继承Thread类,子线程【MyThread-2】执行,优先级【5】

这里需要提一下几个点:

  • 主线程和子线程的顺序是随机的,有可能子线程先执行完,有可能主线程先执行完
  • 线程的优先级默认是5,但是并不是优先级越高,就一定先执行完,只是说概率会大一点,都是cpu资源的抢占
  • 如果和我一样使用org.junit.Test,那么@Test方法所在类中,不能存在有参数构造函数,但是可以存在无参构造,所以要么用main方法测试,要么新建一个类进行测试,关于这几点后面我就不多说了

三、实现Runnable接口

实现Runnable接口,同样需要重写run()方法

package com.alian.csdn.thread;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class MyRunnable implements Runnable {

    private String threadName;

    public MyRunnable(String threadName) {
        this.threadName = threadName;
    }

    @Override
    public void run() {
        log.info("实现Runnable接口,子线程【{}】执行,优先级【{}】", this.threadName, Thread.currentThread().getPriority());
    }
}

我们写个测试方法

package com.alian.csdn.thread;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

@Slf4j
public class MyRunnableTest {

    @Test
    public void testMyRunnable() {
        for (int i = 0; i < 3; i++) {
            Thread thread = new Thread(new MyRunnable("MyRunnable-" + i));
            thread.start();
        }
        log.info("实现Runnable接口,主线程【{}】执行,优先级【{}】", Thread.currentThread().getName(), Thread.currentThread().getPriority());
    }

}

运行结果:

20:00:05.405 [Thread-2] INFO com.alian.csdn.thread.MyRunnable - 实现Runnable接口,子线程【MyRunnable-2】执行,优先级【5】
20:00:05.405 [Thread-1] INFO com.alian.csdn.thread.MyRunnable - 实现Runnable接口,子线程【MyRunnable-1】执行,优先级【5】
20:00:05.405 [Thread-0] INFO com.alian.csdn.thread.MyRunnable - 实现Runnable接口,子线程【MyRunnable-0】执行,优先级【5】
20:00:05.405 [main] INFO com.alian.csdn.thread.MyRunnableTest - 实现Runnable接口,主线程【main】执行,优先级【5】

四、实现Callable<V>接口( 有返回值 )

实现Callable接口,重写V call()方法,其中 V 为返回值

package com.alian.csdn.thread;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.Callable;

@Slf4j
public class MyCallable implements Callable<String> {

    private String threadName;

    public MyCallable(String threadName) {
        this.threadName = threadName;
    }

    @Override
    public String call() throws Exception {
        log.info("实现Callable接口,子线程【{}】执行,优先级【{}】", this.threadName, Thread.currentThread().getPriority());
        return this.threadName + "返回结果:" + (int) (Math.random() * 100);
    }
}

我们先看一个图,从下面这个图我们知道: FutureTask 也是 Runnable的一个实现。
在这里插入图片描述
按照之前的逻辑,我们像下面这样写个测试方法( 错误示例

package com.alian.csdn.thread;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

import java.util.ArrayList;
import java.util.concurrent.FutureTask;

@Slf4j
public class MyCallableTest {

    @Test
    public void testMyCallableLikeSync() throws Exception {
        for (int i = 0; i < 3; i++) {
            String threadName = "myCallable-" + i;
            MyCallable callable = new MyCallable(threadName);
            //创建FutureTask对象
            FutureTask<String> task = new FutureTask<>(callable);
            //启动线程
            new Thread(task).start();
            //获取任务结果
            String result = task.get();
            log.info("result={}", result);
        }
        log.info("实现Callable<V>接口,变成了同步,主线程【{}】执行,优先级【{}】", Thread.currentThread().getName(), Thread.currentThread().getPriority());
    }

}

运行结果:

20:29:36.044 [Thread-0] INFO com.alian.csdn.thread.MyCallable - 实现Callable<V>接口,子线程【myCallable-0】执行,优先级【5】
20:29:36.048 [main] INFO com.alian.csdn.thread.MyCallableTest - result=myCallable-0返回结果:3
20:29:36.049 [Thread-1] INFO com.alian.csdn.thread.MyCallable - 实现Callable<V>接口,子线程【myCallable-1】执行,优先级【5】
20:29:36.049 [main] INFO com.alian.csdn.thread.MyCallableTest - result=myCallable-1返回结果:97
20:29:36.049 [Thread-2] INFO com.alian.csdn.thread.MyCallable - 实现Callable<V>接口,子线程【myCallable-2】执行,优先级【5】
20:29:36.049 [main] INFO com.alian.csdn.thread.MyCallableTest - result=myCallable-2返回结果:85
20:29:36.049 [main] INFO com.alian.csdn.thread.MyCallableTest - 实现Callable<V>接口,变成了同步,主线程【main】执行,优先级【5】

  我们之前继承Thread类和实现Runnable接口执行上都是多线程,但是这个不一样,从上面的结果可以看到,我们的线程看起来好像是同步的(一个线程执行完,下一个线程才能执行),这是因为获取任务的结果阻塞了,直到拿到结果,下一个线程才能创建执行,并不是我们想象的多线程。正确的方式如下:

@Test
    public void testMyCallable() throws Exception {
        //新建一个列表存FutureTask任务
        ArrayList<FutureTask<String>> list = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            String threadName = "myCallable-" + i;
            MyCallable callable = new MyCallable(threadName);
            //创建FutureTask对象
            FutureTask<String> task = new FutureTask<>(callable);
            //启动线程
            new Thread(task).start();
            //加入任务列表
            list.add(task);
        }
        //遍历任务,得到结果
        for (FutureTask<String> task : list) {
            //获取任务结果
            String result = task.get();
            log.info("result={}", result);
        }
        log.info("实现Callable<V>接口,主线程【{}】执行,优先级【{}】", Thread.currentThread().getName(), Thread.currentThread().getPriority());
    }

运行结果:

20:32:36.216 [Thread-0] INFO com.alian.csdn.thread.MyCallable - 实现Callable<V>接口,子线程【myCallable-0】执行,优先级【5】
20:32:36.216 [Thread-2] INFO com.alian.csdn.thread.MyCallable - 实现Callable<V>接口,子线程【myCallable-2】执行,优先级【5】
20:32:36.216 [Thread-1] INFO com.alian.csdn.thread.MyCallable - 实现Callable<V>接口,子线程【myCallable-1】执行,优先级【5】
20:32:36.219 [main] INFO com.alian.csdn.thread.MyCallableTest - result=myCallable-0返回结果:57
20:32:36.219 [main] INFO com.alian.csdn.thread.MyCallableTest - result=myCallable-1返回结果:89
20:32:36.219 [main] INFO com.alian.csdn.thread.MyCallableTest - result=myCallable-2返回结果:81
20:32:36.219 [main] INFO com.alian.csdn.thread.MyCallableTest - 实现Callable<V>接口,主线程【main】执行,优先级【5】

  从这里我们我们可以知道,我们的线程是多线程了,只是有一点,就是在我们获取结果的时候,遍历的正好是我们没有执行的那个任务,其他的可能执行完了,但是任务是执行完了,最终的结果也能到,但是并不一定就是实时拿到。

五、匿名内部类

前面我们都是创建了一个响应的类来创建线程,实际我们还可以更简单的实现,比如匿名内部类。

    @Test
    public void testMyRunnableWithInnerClass(){
        for (int i = 0; i < 3; i++) {
            String threadName="MyRunnable-" + i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    log.info("通过匿名内部类实现,子线程【{}】执行,优先级【{}】", threadName, Thread.currentThread().getPriority());
                }
            }).start();
        }
        log.info("通过匿名内部类实现,主线程【{}】执行,优先级【{}】", Thread.currentThread().getName(),Thread.currentThread().getPriority());
    }

运行结果:

20:40:58.517 [main] INFO com.alian.csdn.thread.MyInnerClass - 通过匿名内部类实现,主线程【main】执行,优先级【5】
20:40:58.517 [Thread-0] INFO com.alian.csdn.thread.MyInnerClass - 通过匿名内部类实现,子线程【MyRunnable-0】执行,优先级【5】
20:40:58.517 [Thread-2] INFO com.alian.csdn.thread.MyInnerClass - 通过匿名内部类实现,子线程【MyRunnable-2】执行,优先级【5】
20:40:58.517 [Thread-1] INFO com.alian.csdn.thread.MyInnerClass - 通过匿名内部类实现,子线程【MyRunnable-1】执行,优先级【5】

实际上,上面的方法用 Lambda 表达式可以更简单

    @Test
    public void testMyRunnableWithLambda(){
        for (int i = 0; i < 3; i++) {
            String threadName="MyRunnable-" + i;
            new Thread(() -> {
                log.info("通过Lambda表达式实现,子线程【{}】执行,优先级【{}】", threadName, Thread.currentThread().getPriority());
            }).start();
        }
        log.info("通过Lambda表达式实现,主线程【{}】执行,优先级【{}】", Thread.currentThread().getName(),Thread.currentThread().getPriority());
    }

运行结果:

20:42:22.672 [Thread-1] INFO com.alian.csdn.thread.MyInnerClass - 通过Lambda表达式实现,子线程【MyRunnable-1】执行,优先级【5】
20:42:22.672 [Thread-2] INFO com.alian.csdn.thread.MyInnerClass - 通过Lambda表达式实现,子线程【MyRunnable-2】执行,优先级【5】
20:42:22.672 [main] INFO com.alian.csdn.thread.MyInnerClass - 通过Lambda表达式实现,主线程【main】执行,优先级【5】
20:42:22.672 [Thread-0] INFO com.alian.csdn.thread.MyInnerClass - 通过Lambda表达式实现,子线程【MyRunnable-0】执行,优先级【5】

六、线程池方式(java.util.concurrent.Executor)

之前的实现多线程的方式可能大家都经常用到,实际还可以使用线程池方式

package com.alian.csdn.thread;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Slf4j
public class MyExecutor {

    @Test
    public void testExecutor(){
        Executor threadPool = Executors.newFixedThreadPool(5);
        for(int i = 0 ;i < 3 ; i++) {
            threadPool.execute(new Runnable() {
                public void run() {
                    log.info("通过Executor实现,子线程【{}】执行,优先级【{}】", Thread.currentThread().getName(), Thread.currentThread().getPriority());
                }
            });
        }
        log.info("通过Executor实现,主线程【{}】执行,优先级【{}】", Thread.currentThread().getName(),Thread.currentThread().getPriority());
    }

}

运行结果:

20:52:09.600 [pool-1-thread-1] INFO com.alian.csdn.thread.MyExecutor - 通过Executor实现,子线程【pool-1-thread-1】执行,优先级【5】
20:52:09.600 [main] INFO com.alian.csdn.thread.MyExecutor - 通过Executor实现,主线程【main】执行,优先级【5】
20:52:09.600 [pool-1-thread-2] INFO com.alian.csdn.thread.MyExecutor - 通过Executor实现,子线程【pool-1-thread-2】执行,优先级【5】
20:52:09.600 [pool-1-thread-3] INFO com.alian.csdn.thread.MyExecutor - 通过Executor实现,子线程【pool-1-thread-3】执行,优先级【5】

当然也能写成 Lambda 表达式的方式

    @Test
    public void testExecutorWithLambda(){
        Executor executorService = Executors.newFixedThreadPool(5);
        for(int i = 0 ;i < 3 ; i++) {
            executorService.execute(() -> {
                log.info("通过Executor和lambda实现,子线程【{}】执行,优先级【{}】", Thread.currentThread().getName(), Thread.currentThread().getPriority());
            });
        }
        log.info("通过Executor和lambda实现,主线程【{}】执行,优先级【{}】", Thread.currentThread().getName(),Thread.currentThread().getPriority());
    }

使用lambda方式运行结果:

20:52:23.630 [pool-1-thread-1] INFO com.alian.csdn.thread.MyExecutor - 通过Executor和lambda实现,子线程【pool-1-thread-1】执行,优先级【5】
20:52:23.630 [pool-1-thread-3] INFO com.alian.csdn.thread.MyExecutor - 通过Executor和lambda实现,子线程【pool-1-thread-3】执行,优先级【5】
20:52:23.630 [main] INFO com.alian.csdn.thread.MyExecutor - 通过Executor和lambda实现,主线程【main】执行,优先级【5】
20:52:23.630 [pool-1-thread-2] INFO com.alian.csdn.thread.MyExecutor - 通过Executor和lambda实现,子线程【pool-1-thread-2】执行,优先级【5】

七、spring异步多线程(@Async)

spring作为目前最流行的框架之一,同样给我们提供了多线程的方案,我们先写个配置类,注意 @EnableAsync 注解的添加。

package com.alian.csdn.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.Executor;
import java.util.concurrent.ThreadPoolExecutor;

@EnableAsync
@Configuration
public class AsyncConfig {

    @Bean
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //设置核心线程数
        executor.setCorePoolSize(5);
        //设置最大线程数
        executor.setMaxPoolSize(10);
        //设置任务队列容量
        executor.setQueueCapacity(10);
        //设置线程活跃时间(秒)
        executor.setKeepAliveSeconds(60);
        //设置默认线程名称(线程前缀名称,有助于区分不同线程池之间的线程比如:taskExecutor-query-)
        executor.setThreadNamePrefix("taskExecutor-async-");
        //设置拒绝策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //设置允许核心线程超时
        executor.setAllowCoreThreadTimeOut(true);
        return executor;
    }

}

在写一个业务处理类,定义三个业务方法

package com.alian.csdn.thread;

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class AsyncThread {

    @Async("taskExecutor")
    public void taskOne() throws Exception {
        log.info("方法taskOne被调用,spring异步多线程@Async,子线程【{}】执行", Thread.currentThread().getName());
    }

    @Async("taskExecutor")
    public void taskTwo() throws Exception {
        log.info("方法taskTwo被调用,spring异步多线程@Async,子线程【{}】执行", Thread.currentThread().getName());
    }

    @Async("taskExecutor")
    public void taskThree() throws Exception {
        log.info("方法taskThree被调用,spring异步多线程@Async,子线程【{}】执行", Thread.currentThread().getName());
    }

}

我们简单点就在main方法里执行测试

package com.alian.csdn;

import com.alian.csdn.thread.AsyncThread;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@Slf4j
@SpringBootApplication
public class CsdnApplication {

    public static void main(String[] args) throws Exception {
        ConfigurableApplicationContext context = SpringApplication.run(CsdnApplication.class, args);
        //获取容器里的对象
        AsyncThread bean = context.getBean(AsyncThread.class);
        log.info("----------------------同一个方法调用------------------------------");
        bean.taskOne();
        bean.taskOne();
        bean.taskOne();
    }

}

运行结果:

21:33:37.498  INFO [           main] com.alian.csdn.CsdnApplication   : ----------------------同一个方法调用------------------------------
21:33:37.504  INFO [xecutor-async-3] com.alian.csdn.thread.AsyncThread: 方法taskOne被调用,spring异步多线程@Async,子线程【taskExecutor-async-3】执行
21:33:37.504  INFO [xecutor-async-2] com.alian.csdn.thread.AsyncThread: 方法taskOne被调用,spring异步多线程@Async,子线程【taskExecutor-async-2】执行
21:33:37.504  INFO [xecutor-async-1] com.alian.csdn.thread.AsyncThread: 方法taskOne被调用,spring异步多线程@Async,子线程【taskExecutor-async-1】执行

我们再测试调用不同的方法

package com.alian.csdn;

import com.alian.csdn.thread.AsyncThread;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@Slf4j
@SpringBootApplication
public class CsdnApplication {

    public static void main(String[] args) throws Exception {
        ConfigurableApplicationContext context = SpringApplication.run(CsdnApplication.class, args);
        //获取容器里的对象
        AsyncThread bean = context.getBean(AsyncThread.class);
        log.info("----------------------不同方法调用------------------------------");
        bean.taskOne();
        bean.taskTwo();
        bean.taskThree();
    }

}

运行结果:

21:35:55.264  INFO [           main] com.alian.csdn.CsdnApplication   : ----------------------不同方法调用------------------------------
21:35:55.270  INFO [xecutor-async-1] com.alian.csdn.thread.AsyncThread: 方法taskOne被调用,spring异步多线程@Async,子线程【taskExecutor-async-1】执行
21:35:55.270  INFO [xecutor-async-2] com.alian.csdn.thread.AsyncThread: 方法taskTwo被调用,spring异步多线程@Async,子线程【taskExecutor-async-2】执行
21:35:55.270  INFO [xecutor-async-3] com.alian.csdn.thread.AsyncThread: 方法taskThree被调用,spring异步多线程@Async,子线程【taskExecutor-async-3】执行

  从上述两个结果可以看到,不管是同一个方法还是不同的方法,每次的线程都不一样,更多关于ThreadPoolTaskExecutor的使用可以参考我之前定时任务的文章:Spring Boot定时任务详解(线程池方式)

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值