为什么要使用多线程?

从计算机来讲,线程是程序执行的最小单位,线程之间的切换和调用的成本低于进程。

从项目上来讲,多线程并发可以提高系统整体的并发能力与性能,可以支持更多的用户。

比如使用Windows右键删除有N个小文件的文件夹时,删除很缓慢,因为Windows使用的是单线程删除。而使用FastCopy进行多线程删除效率非常高,肉眼可见。

Java并发编程就是使用多线程来实现的,当使用多线程就会发现各种问题,其中就有线程管理问题,所以在JDK1.5版本引进的JUC包中提供了Executor这个调度器来创建线程池,对多线程进行管理。

Java创建线程的三种方式

使用Callable和Future(推荐)

实现Callable接口,重写call方法,实现接口的时候可以定义泛型,这个泛型是我们的call方法的返回值,也就是线程返回值。

call方法可以通过返回值获取业务的执行结果,并使用了线程池来管理线程。

// 主线程
public class Demo03 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 创建一个定长线程池,里面有3个线程
        ExecutorService executorService =  Executors.newFixedThreadPool(3);
        T3 t31 = new T3();
        t31.setName("线程1");
        T3 t32 = new T3();
        t32.setName("线程2");

        // 执行线程,并使用Future接受线程内部call方法的返回值
        Future<String> result1 =  executorService.submit(t31);
        Future<String> result2 =  executorService.submit(t32);

        try {
            // 主线程睡3S, 保证线程池中的线程都执行完了
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        //关闭线程池释放所有资源
        executorService.shutdown();
        System.out.println("线程1执行结果:" + result1.get());
        System.out.println("线程2执行结果:" + result2.get());
    }
}

// 实现Callable接口
class T3 implements Callable<String>{

    private String name ;
    public void setName(String name){
        this.name = name;
    }

    @Override
    public String call() throws Exception {
        try {
            System.out.println("实现implements 接口的方式创建线程,参数值:" + this.name);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return this.name + "执行成功";
    }
}

继承Thread类

继承Thread类,重写run方法然后实例化,使用.start启动线程。

无法对线程进行有效的控制,没有返回值、异常,控制不了线程的创建数量,无限制的创建新线程可能会导致OOM。

// 主线程
public class Demo01 {
    public static void main(String[] args) {
        //创建一个新的线程
        T1 t1 = new T1();
        //设置线程名称
//        t1.setName("线程名称");
        //启动线程
        t1.start();

    }
}
// 继承Thread类
class T1 extends Thread{
    @Override
    public void run() {
        try {
            // 打印当前线程的名字
            System.out.println("使用继承Thread的方式创建线程,线程名称:" + this.getName());
        } catch (Exception e) {
            // 捕获并记录异常,避免线程因异常直接终止
            System.err.println("线程运行时发生异常:" + e.getMessage());
            e.printStackTrace();
        }
    }
}

实现Runnable接口

实现implements 接口,实现run方法,实例化传参给Thread,使用.start启动线程。

缺点和继承Thread的方式一样。

// 匿名内部类写法
public class Demo02 {
    public static void main(String[] args) {
        new Thread(() -> {
            try {
                System.out.println("实现implements 接口的方式创建线程,线程名称:" + Thread.currentThread().getName());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
    }
}

------------------------------------------------------------------------------------------------------------------
// 完整写法
// 主线程
public class Demo02 {
    public static void main(String[] args) {
        Thread thread = new Thread(new T2());
        thread.start();
    }
}
// 实现Runnable接口
class T2 implements Runnable {
    @Override
    public void run() {
        try {
            // Thread.currentThread()用于获取当前执行的线程对象
            System.out.println("实现implements 接口的方式创建线程,线程名称:" + Thread.currentThread().getName());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

JUC中的线程池管理

在Java.util.concurrent中,提供了工具类Executors(调度器)对象来创建线程池,可以创建的线程池有四种:

CachedThreadPool

可缓存线程池

FixedThreadPool

定长线程池

SingleThreadExecutor

单线程池

ScheduledThreadPool

调度线程池

可缓存线程池

没有最大线程数限制,池中如果没有空闲线程,会创建新线程加入到池中,如果有空闲线程会复用。

public class CachedThreadPoolDemo {
    public static void main(String[] args) {
        // 创建可缓存线程池
        ExecutorService threadPool = Executors.newCachedThreadPool();

        for (int i = 0; i < 10; i++) {
            final int index = i;
            threadPool.execute(() -> {
                System.out.println("CachedThreadPool Task " + index + " is running on thread " + Thread.currentThread().getName());
            });
        }
        // 关闭线程池
        threadPool.shutdown();
    }
}

定长线程池

创建一个固定大小的线程池,池中有空闲线程就复用,没有则任务陷入等待,任务的默认调度算法是FIFO(先进先出)

}

单线程池

创建一个单线程化的线程池,它只会用唯一的线程来执行任务,保证所有任务按照指定顺序(基于FIFO, 先进先出原则)执行。

适用于需要保证任务串行执行的场景,比如某些数据处理流程要求严格按序执行的情况。

}

调度线程池

创建一个支持定时及周期性任务执行的线程池,主要用于需要延迟执行或者定期重复执行的任务,例如定时检查更新、周期性清理等任务。

可以理解为定时任务调度,实际我们并不使用这种方式进行定时任务调度,有其他更好的方式。

public class ScheduledThreadPoolDemo {
    public static void main(String[] args) {
        // 注意它的返回类不是ExecutorService 而是 ScheduledExecutorService 
        ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(5);

        for (int i = 0; i < 10; i++) {
            final int index = i;
            threadPool.schedule(() -> {
                System.out.println("ScheduledThreadPool Task " + index + " is running on thread " + Thread.currentThread().getName());
            }, 3, TimeUnit.SECONDS); // 这里多了2个参数,表示延迟3秒执行
        }

        // 关闭线程池
        threadPool.shutdown();
    }
}

SpringBoot线程池

SpringBoot 对JAVA中的 ThreadPoolExecutor 进行了封装。

相关注解

@EnableAsync 类上添加,启动类或配置类,开启异步支持

@Async(value = "Bean 名称") 方法上添加,将这个方法执行异步操作

可以创建多个线程池,创建多个的原因是方便不同的业务使用。

如果所有业务共用一个线程池,可能会出现一个业务占满了所有线程,导致其他业务全部在队列等待中,无法访问的情况。

创建线程池

在一个配置类中,可以创建多个线程池,方法名为@Async(value = "Bean 名称") 中的Bean名称。

@Configuration
public class ExecutorConfig {

    @Bean
    public ThreadPoolTaskExecutor smsThreadPoolTaskExecutor() {
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        //配置核心线程数
        threadPoolTaskExecutor.setCorePoolSize(16);
        //配置最大线程数
        threadPoolTaskExecutor.setMaxPoolSize(32);
        //配置队列大小
        threadPoolTaskExecutor.setQueueCapacity(1024);
        //是否允许核心线程超时
        threadPoolTaskExecutor.setAllowCoreThreadTimeOut(true);
        //非核心线程池空闲超时销毁时间(单位秒)
        threadPoolTaskExecutor.setKeepAliveSeconds(15);
        //配置线程池中的线程的名称前缀 (指定一下线程名的前缀)
        threadPoolTaskExecutor.setThreadNamePrefix("async-sms-");
        //配置线程池拒绝策略
        threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //执行初始化
        threadPoolTaskExecutor.initialize();
        //返回线程池对象
        return threadPoolTaskExecutor;
    }

	@Bean
    public ThreadPoolTaskExecutor smsThreadPoolTaskExecutor() {
		......
	}
}

使用线程池

在方法上添加@Async(value = "Bean 名称") ,为需要异步的方法选择线程池

@Async(value = "smsThreadPoolTaskExecutor")
public void doSMS() {
   ......
}

获取多线程的返回值

先要通过实现Future来创建一个返回类

public class ResultFuture<V> implements Future<V> {

    //结果
    private V result;

    public ResultFuture(V result) {
        this.result = result;
    }

    @Override
    public boolean cancel(boolean mayInterruptIfRunning) {
        return false;
    }

    @Override
    public boolean isCancelled() {
        return false;
    }

    @Override
    public boolean isDone() {
        return false;
    }

    @Override
    public V get() throws InterruptedException, ExecutionException {
        return this.result;
    }

    @Override
    public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
        return this.result;
    }
}

在方法中使用返回类

// 修改返回类
@Async(value = "smsThreadPoolTaskExecutor")
public Future<String> doSMS() {
	// new 自定义的返回类,
	return new ResultFuture<>("ok");
}

public void testAsync() {
    Future<String> r = this.doSMS();
    try {
		// 通过get方法获取返回值
        String s = r.get();
    } catch (Exception e) {
        e.printStackTrace();
    }
}