聊聊Java多线程
多线程
什么是多线程
多线程是指从软硬件上实现多条执行流程的技术。通俗来说,单线程就是一个人做一件事,多线程是多个人做同一件事。
为什么要用多线程
多线程可以避免阻塞,将耗时的操作,提高应用程序响应;
多CPU系统中,使用线程提高CPU利用率;
线程创建方式
方式一 :继承Thread类
1、定义一个子类MyThread继承线程类java.lang.Thread,重写run()方法
2、 创建MyThread类的对象
3、调用线程对象的start()方法启动线程(启动后还是执行run方法的)
/**
* @author Yeah
* @date 2024/10/27 13:14
**/
public class MyThread extends Thread {
@Override
public void run() {
//需要待执行的任务
}
public static void main(String[] args) {
MyThread myThread = new MyThread("设置线程名字");
myThread.start();
}
}
注意:
1、为什么不直接调用了run方法,而是调用start启动线程?
直接调用run方法会当成普通方法执行,此时相当于还是单线程执行。 只有调用start方法才是启动一个新的线程执行。
2、为什么把主线程任务放在子线程之前了?
这样主线程一直是先跑完的,相当于是一个单线程的效果。
优点:编码简单。
缺点:线程类已经继承Thread类,无法继承其他类,不利于扩展。
方式二 :实现Runnable接口
1、定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法
2、创建MyRunnable任务对象
3、把MyRunnable任务对象交给Thread处理
4、 调用线程对象的start()
/**
* @author Yeah
* @date 2024/10/27 13:14
**/
public class MyRunnable implements Runnable {
@Override
public void run() {
//需要待执行的任务
}
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
new Thread(myRunnable, "设置线程名字").start();
//方式二:实现Runnable接口(匿名内部类形式)
new Thread(()->{
//需要待执行的任务
}, "设置线程名字").start();
}
}
优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强。
缺点:编程多一层对象包装,如果线程有执行结果是不可以直接返回的。
方式三 :实现Callable、FutureTask接口实现
1、定义一个线程任务类实现Callable接口,重写call方法,封装要做的事情。
2、 用FutureTask把Callable对象封装成线程任务对象。
3、把线程任务对象交给Thread处理。
4、调用Thread的start方法启动线程,执行任务。
5 、线程执行完毕后、通过FutureTask的get方法去获取任务执行的结果。
/**
* @author Yeah
* @date 2024/10/27 13:14
**/
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> future = new FutureTask<String>(() -> {
//需要待执行的任务
return "hello";
});
new Thread(future, "设置线程名字").start();
//获取结果
future.get();
//获取线程名字
String name = Thread.currentThread().getName();
}
优点:1、线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强。
2、可以在线程执行完毕后去获取线程执行的结果以及异常信息。
缺点:编码复杂一点。
线程池
线程池就是一个可以复用线程的技术。
为什么要使用线程池:
如果用户每发起一个请求,后台就创建一个新线程来处理,下次新任务来了又要创建新线程,而创建新线程的开销是很大的,这样会严重影响系统的性能。
线程池的创建
/**
* @author Yeah
* @date 2024/10/27 13:14
**/
public static void main(String[] args) throws ExecutionException, InterruptedException {
//推荐使用ThreadPoolExecutor自定义一个线程池对象,阿里开发规范不推荐使用Executors(线程池的工具类)
ThreadPoolExecutor threadPoolExecutor =
new ThreadPoolExecutor(16,
32,
120,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
//处理Runnable任务 ---> 无返回值
threadPoolExecutor.execute(() -> {
//待执行的任务
});
//处理Callable任务 ---> 有返回值 有异常信息
Future<String> future = threadPoolExecutor.submit(() -> {
//待执行的任务
return "hello";
});
//获取结果
future.get();
}
参数解释
1、ThreadPoolExecutor构造器的参数说明:
参数一:指定线程池的线程数量(核心线程): corePoolSize
参数二:指定线程池可支持的最大线程数: maximumPoolSize
参数三:指定临时线程的最大存活时间: keepAliveTime
参数四:指定存活时间的单位(秒、分、时、天): unit
参数五:指定任务队列: workQueue
参数六:指定用哪个线程工厂创建线程: threadFactory
参数七:指定线程忙,任务满的时候,新任务来了怎么办: handler
2、新任务拒绝策略
①、ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。是默认的策略
②、ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常 这是不推荐的做法
③、ThreadPoolExecutor.DiscardOldestPolicy:抛弃队列中等待最久的任务 然后把当前任务加入队列中
④、ThreadPoolExecutor.CallerRunsPolicy:由主线程负责调用任务的run()方法从而绕过线程池直接执行
3、临时线程什么时候创建?
新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。
4、什么时候会开始拒绝任务?
核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始任务拒绝。
实际开发中线程池的配置
实际开发中常使用 Spring 提供的 ThreadPoolTaskExecutor的线程池,用于异步执行任务或处理并发请求。在使用 ThreadPoolTaskExecutor作为 Spring Bean 注册到容器中后,Spring 会负责在应用程序关闭时自动关闭所有注册的线程池,所以不需要手动关闭。 这样不仅可以确保线程池中的线程正确地停止,还可以防止资源泄露和潜在的并发问题。
/**
* 线程池参数配置
*
* @author Yeah
* @date 2024/10/27 13:14
**/
@Data
@Configuration
@ConfigurationProperties(prefix = "thread.pool")
public class ThreadPoolProperties {
/**
* 核心线程池大小
*/
private int corePoolSize;
/**
* 最大可创建的线程数
*/
private int maxPoolSize;
/**
* 队列最大长度
*/
private int queueCapacity;
/**
* 线程池维护线程所允许的空闲时间
*/
private int keepAliveSeconds;
/**
* 线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
*/
private String threadNamePrefix;
}
# ========================Thread Pool Config==================
# 线程池配置 System.out.println(Runtime.getRuntime().availableProcessors());
thread.pool.corePoolSize=16
thread.pool.maxPoolSize=32
thread.pool.queueCapacity=50
thread.pool.keepAliveSeconds=2
thread.pool.threadNamePrefix="thread-name-"
/**
* 线程池配置
*
* @author Yeah
* @date 2024/10/27 13:14
**/
@Configuration
public class ThreadPoolConfig {
@Resource
private ThreadPoolProperties threadPoolProperties;
@Bean("threadPoolTaskExecutor")
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor();
// 核心线程池大小
threadPool.setCorePoolSize(threadPoolProperties.getCorePoolSize());
// 最大可创建的线程数
threadPool.setMaxPoolSize(threadPoolProperties.getMaxPoolSize());
// 等待队列最大长度
threadPool.setQueueCapacity(threadPoolProperties.getQueueCapacity());
// 线程池维护线程所允许的空闲时间
threadPool.setKeepAliveSeconds(threadPoolProperties.getKeepAliveSeconds());
//异步方法内部线程名称
threadPool.setThreadNamePrefix(threadPoolProperties.getThreadNamePrefix());
// 线程池对拒绝任务(无线程可用)的处理策略
threadPool.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 任务都完成再关闭线程池
threadPool.setWaitForTasksToCompleteOnShutdown(true);
// 任务初始化
threadPool.initialize();
return threadPool;
}
}
线程池的优雅关闭
/**
* @author Yeah
* @date 2024/10/27 13:14
**/
public class ThreadUtil {
/**
* 最大等待时间
*/
private static final int MAX_WAITING_MILLIS = 120;
public static void shutdownAndAwaitTermination(ExecutorService threadPool) {
if (threadPool != null && !threadPool.isShutdown()) {
threadPool.shutdown();
try {
if (!threadPool.awaitTermination(MAX_WAITING_MILLIS, TimeUnit.SECONDS)) {
threadPool.shutdownNow();
if (!threadPool.awaitTermination(MAX_WAITING_MILLIS, TimeUnit.SECONDS)) {
System.out.println("Pool did not terminate");
}
}
} catch (InterruptedException ie) {
threadPool.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
}
原理解释
private void test() throws Exception {
ExecutorService executorService = Executors.newFixedThreadPool(1);
executorService.execute(() -> {
// 待处理的任务
});
//执行shutdown,将会拒接新任务提交到线程池;待执行的任务不会取消,正在执行的任务也不会取消,继续执行到结束。
executorService.shutdown();
// 执行shutdownNow,将会拒绝新任务提交到线程池,取消待执行的任务,尝试取消执行中的任务。
executorService.shutdownNow();
// 等待线程池终止,最多等待10秒
executorService.awaitTermination(10, TimeUnit.SECONDS);
}
线程池异常如何处理
1、 默认调用,execute会抛出异常。
2、 默认调用,submit会吞掉异常。
3、submit执行后,如果get方法调用想要获得返回值,会抛出异常。
重写afterExecute方法,统一处理异常:
ThreadPoolExecutor threadPool =
new ThreadPoolExecutor(16,
32,
120,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
){
@Override
protected void afterExecute(Runnable runnable, Throwable throwable)
{
//execute运行
if (throwable != null)
{
log.error(throwable.getMessage(), throwable);
}
//submit运行
if (throwable == null && runnable instanceof Future<?>)
{
try
{
Future<?> future = (Future<?>) runnable;
if (future.isDone())
{
future.get();
}
} catch (CancellationException ce){
throwable = ce;
ce.printStackTrace();
} catch (ExecutionException ee){
throwable = ee.getCause();
ee.printStackTrace();
} catch (InterruptedException ie){
ie.printStackTrace();
Thread.currentThread().interrupt();
}
}
}
};