聊聊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();
                    }
                }
            }
        };
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值