线程池详解并解决发问题

本文已收录于专栏
《Java》

概念说明

什么是线程池

  线程池是一种用于管理和复用线程的机制。它由一个线程队列和一组管理线程的方法组成。线程池中的线程可以被重复使用,用于执行提交的任务,而不需要每次都创建和销毁线程。

线程池组成部分

  线程队列:线程池维护一个线程队列,用于存储待执行的任务。当有任务提交到线程池时,线程池会从队列中取出一个空闲的线程来执行任务。
  线程管理器:线程池的线程管理器负责创建、启动和停止线程。它会根据需要动态地创建线程,并将线程添加到线程队列中。当线程池不再需要某个线程时,线程管理器会停止该线程并从线程队列中移除。
  任务接口:线程池中的任务接口定义了待执行的任务的类型。任务可以是实现了Runnable接口的普通任务,也可以是实现了Callable接口的有返回值的任务。
  任务队列:线程池的任务队列用于存储待执行的任务。当有任务提交到线程池时,线程池会将任务加入到任务队列中,等待线程来执行。
在这里插入图片描述

优势利弊

线程池优点

  • 降低资源消耗:线程池可以重复利用已创建的线程,避免了线程的频繁创建和销毁的开销。线程的创建和销毁都需要消耗系统资源,包括CPU、内存等。通过使用线程池,可以将线程的创建和销毁集中在一处,减少了资源的消耗,提高了系统的性能和效率。
  • 提高响应速度:线程池可以提高任务的响应速度。线程池中的线程是预先创建好的,当有任务到达时,可以立即分配一个空闲的线程来执行任务,而不需要等待线程的创建和启动。这样可以减少任务的等待时间,提高任务的响应速度。
  • 控制并发线程数量:线程池可以控制并发线程的数量,避免了系统中线程数量过多导致的资源竞争和性能下降问题。通过设置线程池的大小,可以限制同时执行的线程数量,避免系统过载。同时,线程池还可以根据系统的负载情况动态调整线程的数量,保持系统的稳定性和性能。
  • 提供线程管理和监控功能:线程池提供了一些管理和监控线程的方法,可以更好地控制和优化线程的执行。例如,可以设置线程的优先级、超时时间、线程名称等属性。同时,线程池还提供了一些监控方法,可以获取线程池中线程的状态、执行情况等信息,方便进行线程的调试和优化。

线程池缺点

  • 资源占用:线程池会预先创建一定数量的线程,这些线程会一直存在,即使没有任务需要执行。这样会占用一定的系统资源,例如内存和CPU资源。
    举例:假设一个线程池配置了10个线程,但在某个时间段内只有2个任务需要执行,其他8个线程就会一直空闲占用资源。
  • 线程泄露:线程池中的线程是可以复用的,当一个任务执行完成后,线程会返回线程池并标记为可用状态。然而,如果在任务执行过程中发生了异常,并没有正确地将线程返回线程池,就会导致线程泄露。
    举例:某个任务在执行过程中发生了异常,并没有将线程返回线程池。这样,该线程就无法被复用,会一直占用着系统资源。
  • 阻塞问题:线程池中的线程是有限的,当任务过多时,线程池可能会因为线程不足而无法及时处理任务,导致任务被阻塞。
    举例:线程池中只有5个线程,但同时有10个任务需要执行。前5个任务可以被立即执行,但后面的5个任务需要等待前面的任务执行完成后才能执行,导致任务被阻塞。

具体应用

创建线程池

创建线程池的6种方式
在这里插入图片描述

创建线程池的参数
在这里插入图片描述

提交任务

提交任务的两种方式

1.executor:提交Runnable任务,是向线程池中提交无返回值的任务

具体业务类

public class TaskToRunnable  implements Runnable{
    
    

    @Override
    public void run() {
    
    
        //打印线程的名字
        System.out.println("线程名:"+Thread.currentThread().getName());
    }
}

客户端

public class Client {
    
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        //创建任务无返回值
        TaskToRunnable taskToRunnable=new TaskToRunnable();
        //创建单个线程的线程池
        ExecutorService threadPool = Executors.newSingleThreadExecutor();
        //提交任务
        threadPool.execute(taskToRunnable);
        //关闭线程池
        threadPool.shutdown();
    }
}

运行结果
在这里插入图片描述

2.submit:提交Callable任务,是向线程池中提交有返回值的任务

具体业务类

public class TaskToCallable implements Callable<Integer> {
    
    
    @Override
    public Integer call() {
    
    
        return 5+5;
    }
}

客户端

public class Client {
    
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    

        //创建任务有返回值
        TaskToCallable taskToCallable=new TaskToCallable();
        //创建单个线程的线程池
        ExecutorService threadPool = Executors.newSingleThreadExecutor();
        //提交任务
        Future<Integer> submit = threadPool.submit(taskToCallable);
        try{
    
    
            System.out.println("返回值为:"+submit.get());
        }catch (Exception e){
    
    
            e.printStackTrace();
        }finally {
    
    
            //关闭线程池
            threadPool.shutdown();
        }
    }
}

运行结果
在这里插入图片描述

取消任务

取消任务分为三个阶段包括:取消未执行的任务、取消正在运行的任务和取消已完成的任务下面通过代码的方式给大家展示他们的效果

取消未执行的任务

客户端代码

/**
 * @BelongsProject: demo
 * @BelongsPackage: com.example.threadpool.cancel
 * @Author: Wuzilong
 * @Description: 取消未执行的任务
 * @CreateTime: 2023-07-23 23:42
 * @Version: 1.0
 */

public class UnExecutedClient {
    
    
    public static void main(String[] args) {
    
    
        TaskToCallable taskToCallable=new TaskToCallable();
        ExecutorService threadExecutor= Executors.newSingleThreadExecutor();
        Future<Integer> future1 = threadExecutor.submit(taskToCallable);
        Future<Integer> future2 = threadExecutor.submit(taskToCallable);
        boolean cancel = future2.cancel(false);
        System.out.println("任务2是否取消成功"+cancel);
        try{
    
    
            Integer integer = future2.get();
            System.out.println("任务2的返回值"+integer);
        }catch (Exception e){
    
    
            e.printStackTrace();
        }finally {
    
    
            threadExecutor.shutdown();
        }
    }
}

业务代码三个实例都是一样的下面的两个示例就不在写了

/**
 * @BelongsProject: demo
 * @BelongsPackage: com.example.threadpool.commit
 * @Author: Wuzilong
 * @Description: 业务代码
 * @CreateTime: 2023-07-23 22:58
 * @Version: 1.0
 */

public class ExecuteClientTask implements Callable<Integer> {
    
    
    @Override
    public Integer call() throws Exception {
    
    
        int i=0;
        //线程没有被中断递增i
        while(!Thread.interrupted()){
    
    
            i++;
        }
        System.out.println("当前i的值为:"+i);
        return 5+5;
    }
}

运行结果
在这里插入图片描述

  我们创建了一个单个线程的线程池,并创建了两个线程。这样一个线程在线程队列中一个线程在任务队列中,我们取消在任务队列中的线程,获取获取一下取消了的线程的值。会发现获取值的时候会报错,提示线程已取消。

取消正在执行的任务

客户端代码


/**
 * @BelongsProject: demo
 * @BelongsPackage: com.example.threadpool.cancel
 * @Author: Wuzilong
 * @Description: 取消执行中的任务
 * @CreateTime: 2023-07-23 23:50
 * @Version: 1.0
 */

public class ExecuteClient {
    
    
    public static void main(String[] args) {
    
    
        ExecuteClientTask taskToCallable=new ExecuteClientTask();
        ExecutorService threadExecutor= Executors.newSingleThreadExecutor();
        Future<Integer> future = threadExecutor.submit(taskToCallable);
        System.out.println("任务是否完成:"+future.isDone());
        //参数为false执行结果显示取消成功但是程序没有结束    任务继续执行完
        //参数为true直接结果显示取消成功程序结束了
        boolean cancel = future.cancel(true);
        System.out.println("任务1是否取消成功"+cancel);
        try{
    
    
            Integer integer = future.get();
            System.out.println("获取任务1的结果:"+integer);

        }catch (Exception e){
    
    
            e.printStackTrace();
        }finally {
    
    
            threadExecutor.shutdown();
        }
    }
}

运行结果
在这里插入图片描述
  我们同样创建了一个当个线程的线程池,提交完任务之后直接执行取消操作,我们在执行取消操作的时候会传入一个参数,true或者false:参数为false执行结果显示取消成功但是程序没有结束,任务继续执行完。参数为true直接结果显示取消成功程序结束了。上面示例中传入的是true下面在获取值的时候是获取不到的,直接报线程取消的错。

取消已完成的任务

客户端代码

/**
 * @BelongsProject: demo
 * @BelongsPackage: com.example.threadpool.cancel
 * @Author: Wuzilong
 * @Description: 取消已完成的任务  无法取消
 * @CreateTime: 2023-07-23 23:54
 * @Version: 1.0
 */

public class ExecutedClient {
    
    
    public static void main(String[] args) {
    
    
        TaskToCallable taskToCallable=new TaskToCallable();
        ExecutorService threadExecutor= Executors.newSingleThreadExecutor();
        Future<Integer> future = threadExecutor.submit(taskToCallable);

        try{
    
    
            Integer integer = future.get();
            System.out.println("获取任务1的结果:"+integer);
            //参数只对运行时的任务有效
            boolean cancel = future.cancel(false);
            System.out.println("任务1是否取消成功"+cancel);
            System.out.println("任务1的返回值"+integer);
        }catch (Exception e){
    
    
            e.printStackTrace();
        }finally {
    
    
            threadExecutor.shutdown();
        }
    }
}

运行结果
在这里插入图片描述
  我们同样创建了一个当个线程的线程池,提交完任务之后获取了一下任务的返回结果,然后在去取消任务。发现取消之后还是能够获取到任务的返回值的,所以执行完的任务是无法取消的。

在这里插入图片描述

拒绝任务

业务逻辑

public class Task implements Runnable{
    
    
    private final int index;

    public Task(int index){
    
    
        this.index=index;
    }

    @Override
    public void run() {
    
    
        System.out.println("输出线程名:"+Thread.currentThread().getName()+"线程编号:"+index);
    }
}

客户端

/**
 * @BelongsProject: demo
 * @BelongsPackage: com.example.threadpool.refuse
 * @Author: Wuzilong
 * @Description: 线程池拒绝任务
 * @CreateTime: 2023-07-24 01:08
 * @Version: 1.0
 */

public class Client {
    
    
    public static void main(String[] args) {
    
    
        //直接丢弃,只有前两个线程
//        ThreadPoolExecutor threadPoolExecutor=new ThreadPoolExecutor(1,1,0L, TimeUnit.SECONDS,new LinkedBlockingQueue<>(1),new ThreadPoolExecutor.DiscardPolicy());
        //拒绝的任务交个调用者的线程去执行
//        ThreadPoolExecutor threadPoolExecutor=new ThreadPoolExecutor(1,1,0L, TimeUnit.SECONDS,new LinkedBlockingQueue<>(1),new ThreadPoolExecutor.CallerRunsPolicy());
        //默认的拒绝策略,直接抛出异常信息
//        ThreadPoolExecutor threadPoolExecutor=new ThreadPoolExecutor(1,1,0L, TimeUnit.SECONDS,new LinkedBlockingQueue<>(1),new ThreadPoolExecutor.AbortPolicy());
        //丢弃队列头部的任务,添加新的任务
        ThreadPoolExecutor threadPoolExecutor=new ThreadPoolExecutor(1,1,0L, TimeUnit.SECONDS,new LinkedBlockingQueue<>(2),new ThreadPoolExecutor.DiscardOldestPolicy());

        try{
    
    
            threadPoolExecutor.execute(new Task(1));
            threadPoolExecutor.execute(new Task(2));
            threadPoolExecutor.execute(new Task(3));
            //丢弃头部的任务需要打开,前三个注释掉即可
            threadPoolExecutor.execute(new Task(4));

        }catch (Exception e){
    
    
            e.printStackTrace();
        }finally {
    
    
            //关闭线程
            threadPoolExecutor.shutdown();
        }
    }
}

运行结果
在这里插入图片描述
  我们现在使用的是DiscardOldestPolicy拒绝策略创建了一个一个线程队列并且两个任务队列的线程池我们向线程池中添加了四个线程,执行的结果是第二个线程没有执行,执行了第四个线程,这和我们设置的DiscardOldestPolicy拒绝策略是一致的,把任务队列中的头线程给取消了新的线程添加到任务队列中了。
在这里插入图片描述

关闭操作

  关闭线程池之后,线程池不会继续接收新的任务,并且会继续执行完任务队列中的任务

/**
 * @BelongsProject: demo
 * @BelongsPackage: com.example.threadpool.close
 * @Author: Wuzilong
 * @Description: 关闭线程池,不继续接收新的任务,会继续执行完任务队列中的任务
 * @CreateTime: 2023-07-24 08:54
 * @Version: 1.0
 */

public class Client {
    
    
    public static void main(String[] args) {
    
    
        ThreadPoolExecutor threadPoolExecutor=new ThreadPoolExecutor(1,1,0L, TimeUnit.SECONDS,new LinkedBlockingQueue<>(1),new ThreadPoolExecutor.AbortPolicy());
        try{
    
    
            threadPoolExecutor.execute(new Task(1));
            //将任务队列中的任务继续执行完毕
            threadPoolExecutor.execute(new Task(3));
        }catch (Exception e){
    
    
            e.printStackTrace();
        }finally {
    
    
            threadPoolExecutor.shutdown();
            //关闭线程之后继续提交任务看看是否被拒绝
            //threadPoolExecutor.execute(new Task(2));
        }
    }
}

运行结果
1.继续执行任务的结果
在这里插入图片描述
2.拒绝任务的结果
在这里插入图片描述

延迟操作

执行一次任务

客户端代码

public class Demo {
    
    
    public static void main(String[] args) {
    
    
        Task task=new Task();
        ScheduledExecutorService scheduledExecutorService= Executors.newScheduledThreadPool(5);
        System.out.println("提交任务时间"+LocalTime.now());
        scheduledExecutorService.schedule(task,3, TimeUnit.SECONDS);
        scheduledExecutorService.shutdown();
    }
}

具体业务代码

public class Task implements Runnable{
    
    
    @Override
    public void run() {
    
    
        System.out.println("执行方法时间"+ LocalTime.now());
    }
}

运行结果
在这里插入图片描述
提交Runnable任务和提交Callable任务是一样的。只不过把实现的接口调整一下即可。这里就不过多的演示。schedule方法中的参数:第一个是任务对象,第二个是时间,第三个是时间单位。

重复性执行任务

固定时间

具体业务代码

public class Task implements Runnable{
    
    
    @Override
    public void run() {
    
    
        System.out.println("执行方法时间"+ LocalTime.now());
        try{
    
    
            //线程休眠1秒钟,模拟任务执行时长
            Thread.sleep(1000);
        }catch (Exception e){
    
    
            e.printStackTrace();
        }
    }
}

客户端代码

public class Demo {
    
    
    public static void main(String[] args) {
    
    
        Task task=new Task();
        ScheduledExecutorService scheduledExecutorService= Executors.newScheduledThreadPool(5);
        System.out.println("提交任务时间"+LocalTime.now());
        scheduledExecutorService.scheduleAtFixedRate(task,1,1, TimeUnit.SECONDS);
        try{
    
    
            //当前下次线程休眠5秒
            TimeUnit.SECONDS.sleep(5);
        }catch (Exception e){
    
    
            e.printStackTrace();
        }

        scheduledExecutorService.shutdown();
    }
}

运行结果
在这里插入图片描述
每隔一秒钟执行一次,scheduleAtFixedRate方法中传入的参数为需要执行的任务和延迟时间和每次延迟的固定时间,示例中传入的是1表示每间隔一秒执行一次,最有一个就是时间的单位了。

间隔时间

客户端代码

public class Demo {
    
    
    public static void main(String[] args) {
    
    
        Task task=new Task();
        ScheduledExecutorService scheduledExecutorService= Executors.newScheduledThreadPool(5);
        System.out.println("提交任务时间"+LocalTime.now());
        scheduledExecutorService.scheduleWithFixedDelay(task,2,1, TimeUnit.SECONDS);
        try{
    
    
            //当前下次线程休眠5秒
            TimeUnit.SECONDS.sleep(5);
        }catch (Exception e){
    
    
            e.printStackTrace();
        }

        scheduledExecutorService.shutdown();
    }
}

具体业务代码和固定时间中的是一致的这里就不重复写了。
运行结果
在这里插入图片描述

在前一个任务执行完的前提下每隔两秒钟执行一次,scheduleWithFixedDelay方法中传入的参数为需要执行的任务和延迟时间和每次延迟的固定时间,示例中传入的是2表示在前一个任务执行完的前提下每隔两秒钟执行一次,最有一个就是时间的单位了。

在这里插入图片描述

固定时间和间隔时间的对比

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_45490198/article/details/131863455