
JAVA线程和线程池
为什么要使用多线程?
从计算机来讲,线程是程序执行的最小单位,线程之间的切换和调用的成本低于进程。
从项目上来讲,多线程并发可以提高系统整体的并发能力与性能,可以支持更多的用户。
比如使用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(调度器)对象来创建线程池,可以创建的线程池有四种:
可缓存线程池
没有最大线程数限制,池中如果没有空闲线程,会创建新线程加入到池中,如果有空闲线程会复用。
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();
}
}