Java并发编程(八):线程池与线程调度

线程池与线程调度

线程池的好处

  • 避免频繁地创建和销毁线程
  • 创建了的线程,用完后可以重复利用
  • 可以类比数据库连接池理解

直接看具体使用代码

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

/**
 * 一、线程池 :提供了一个线程队列,队列中保存着所有等待状态的线程
 *        避免了创建和销毁线程的额外开销,提高了响应速度
 * 二、线程池体系结构:
 *        java.util.concurrent.Executor :负责线程的使用与调度的根接口
 *        |-- ExecutorService 子接口: 线程池的主要接口
 *          |-- ThreadPoolExecutor : 线程池的实现类
 *          |-- ScheduledExecutorService 子接口 : 负责线程的调度
 *              |-- ScheduledThreadPoolExecutor : 继承了线程池的实现类ThreadPoolExecutor,
 												   实现了ScheduledExecutorService接口
 *  三、工具类:Executors
 *      ExecutorService newFixedThreadPool():创建固定大小的线程池
 *      ExecutorService newCachedThreadPool():缓存线程池,线程池数量不固定,可根据需求自动更改数量
 *      ExecutorService newSingleThreadExecutor():创建单个线程池。线程池中只有一个线程
 *
 *      ScheduledExecutorService newScheduledThreadPool():创建固定大小的线程,延迟或定时地执行任务
 *
 */
public class TestThreadPool {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ThreadPoolDemo tpd = new ThreadPoolDemo();
        //1.创建线程池
        ExecutorService pool = Executors.newFixedThreadPool(5);

        List<Future> list = new ArrayList<>();

        for(int i=0;i<10;i++)
        {
            Future<Integer> future = pool.submit(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    int sum = 0;
                    for(int i=0;i<100;i++)
                    {
                        sum+=i;
                    }
                    return sum;
                }
            });

            list.add(future);
        }

       for(Future future:list)
       {
           System.out.println(future.get());
       }

        //2.为线程池中的线程分配任务
//        for(int i = 0;i<100;i++)
//        {
//            pool.submit(tpd);
//        }

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

class ThreadPoolDemo implements Runnable{
    private int i =0;

    @Override
    public void run() {
        while(i<=100){
            System.out.println(Thread.currentThread().getName()+":"+ i++);
        }
    }
}
代码逻辑:
  • 创建了一个核心线程池数为5的线程池
  • 分别使用submit方法添加线程,有的添加Runnable,有的添加Callable,发现Callable有返回结果可以获取

注意点:

  • 使用线程池启动的不同方式
    • submit和execute都可以添加线程任务,但submit有返回值,execute没有
    • submit返回的是一个future ,可以通过这个future取到线程执行的结果或者异常信息
    • 使用submit提交子任务,一定要获取返回值Future,通过get方法获取可能出现的异常,并且可以进行捕获(推荐)
    • 使用execute执行子任务,异常可以被抛出,但是主线程不能捕获子任务线程中的异常
    • 使用submit提交子任务,只是提交,不获取返回值future,异常会被封装在子线程内部,不会抛出,主线程也无法捕获
    • shutdown 已提交的任务继续执行,不接受新任务提交
  • 在实际使用中,最好使用ThreadPoolExecutor来创建线程池
  • 一方面是由于jdkExecutor框架虽然提供了如newFixedThreadPool()newSingleThreadExecutor()newCachedThreadPool()等创建线程池的方法,但都有其局限性,不够灵活;另外由于前面几种方法内部也是通过ThreadPoolExecutor方式实现,使用ThreadPoolExecutor有助于大家明确线程池的运行规则,创建符合自己的业务场景需要的线程池,避免资源耗尽的风险

线程调度

线程调度主要关注线程的执行方式和策略

看如下代码

import java.util.Random;
import java.util.concurrent.*;

public class TestScheduledThreadPool {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);

        for(int i =0;i<10;i++)
        {
            Future<Integer> result = pool.schedule(new Callable<Integer>() {

                @Override
                public Integer call() throws Exception {
                    int num = new Random().nextInt(100);
                    System.out.println(Thread.currentThread().getName()+":"+num);
                    return num;
                }
            },1, TimeUnit.SECONDS);

        }

        pool.shutdown();

    }
}

代码逻辑:

  • 起一个线程池,依次往该池中添加线程(Callable),输出当前线程名+一个随机数

  • 规定每次线程执行前都等待一秒时间

实际执行效果:

pool-1-thread-1:31
31
pool-1-thread-1:10
10
pool-1-thread-2:13
13
pool-1-thread-1:89
89
pool-1-thread-3:83
83
pool-1-thread-2:8
8
pool-1-thread-4:63
63
pool-1-thread-1:76
76
pool-1-thread-5:26
26
pool-1-thread-3:81
81

每打印一次都等待一秒

线程池的调度可以做到延迟执行,定时执行等功能

原创文章 40 获赞 16 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_43925277/article/details/105181810