多线程---线程池的使用

线程池的使用

  • 一、Executors - 线程池的工厂
  • 二、JDK自带的一些线程池
    • 1. SingleThreadExecutor
    • 2. FixThreadPool
    • 3. CachedThreadPool
    • 4. ScheduledThreadPool
    • 5. WorkStealingPool
    • 6. ForkJoinPool
      • 并行流处理API
  • 三、自定义线程池(建议)
    • 1. ThreadPoolExecutor

一、Executors - 线程池的工厂

多线程---线程池的使用_第1张图片

二、JDK自带的一些线程池

1. SingleThreadExecutor

多线程---线程池的使用_第2张图片

可以看到核心线程1个,最大线程同样也是1个,空闲等待时间为0毫秒, 即执行完本条任务后, 不继续原地等待,new LinkedBlockingQueue() 说明SingleThreadExecutor自己维护了一个链表阻塞队列, 无需手动去管理。

只有一个线程的线程池

为什么要有单线程的线程池?

  1. 线程池有属于自己的任务队列, 不需要自己去手动维护一个任务队列
  2. 有完整的生命周期管理
  • 代码演示
  /**
   * SingleThreadExecutor 只有一个线程的线程池
   */
  public class T07_SingleThreadPool {
      public static void main(String[] args) {
          ExecutorService service = Executors.newSingleThreadExecutor();
          for (int i = 0; i < 5; i++) {
              final int j = i;
              service.execute(() -> {
  
                  System.out.println(j + " " + Thread.currentThread().getName());
              });
          }
      }
  }
  • 输出结果

    0 pool-1-thread-1
    1 pool-1-thread-1
    2 pool-1-thread-1
    3 pool-1-thread-1
    4 pool-1-thread-1

2. FixThreadPool

多线程---线程池的使用_第3张图片

固定数量线程的线程池, 构造函数只有一个参, 即线程数量

  • 代码演示

/**
 * FixedThreadPool 固定线程数量的线程池
 * 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
 * 定长线程池的大小最好根据系统资源进行设置
 */
public class T09_FixedThreadPool {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        long start = System.currentTimeMillis();
        //单线程执行统计1到200000之间的质数耗时
        getPrime(1, 200000);
        long end = System.currentTimeMillis();
        System.out.println(end - start);

        //根据机器CPU 核数量
        final int cpuCoreNum = 4;

        //使用FixedThreadPool多线程执行,统计1到200000之间的质数耗时
        ExecutorService service = Executors.newFixedThreadPool(cpuCoreNum);

        MyTask t1 = new MyTask(1, 80000); //1-5 5-10 10-15 15-20
        MyTask t2 = new MyTask(80001, 130000);
        MyTask t3 = new MyTask(130001, 170000);
        MyTask t4 = new MyTask(170001, 200000);

        Future<List<Integer>> f1 = service.submit(t1);
        Future<List<Integer>> f2 = service.submit(t2);
        Future<List<Integer>> f3 = service.submit(t3);
        Future<List<Integer>> f4 = service.submit(t4);

        start = System.currentTimeMillis();
        f1.get();
        f2.get();
        f3.get();
        f4.get();
        end = System.currentTimeMillis();
        System.out.println(end - start);
        service.shutdown();
    }

    static class MyTask implements Callable<List<Integer>> {
        //起始数, 结束数
        int startPos, endPos;

        MyTask(int s, int e) {
            this.startPos = s;
            this.endPos = e;
        }

        @Override
        public List<Integer> call() {
            //统计质数
            List<Integer> r = getPrime(startPos, endPos);
            return r;
        }

    }

    static boolean isPrime(int num) {
        for (int i = 2; i <= num / 2; i++) {
            if (num % i == 0) {
                return false;
            }
        }
        return true;
    }

    static List<Integer> getPrime(int start, int end) {
        List<Integer> results = new ArrayList<>();
        for (int i = start; i <= end; i++) {
            if (isPrime(i)) {
                results.add(i);
            }
        }

        return results;
    }
}

  • 输出结果

    2002
    716

3. CachedThreadPool

多线程---线程池的使用_第4张图片

这里核心线程为0, 最大线程为Integer的最大值, 基本上无限大,等待队列采用的是SynchronousQueue(),对于SynchronousQueue,每次put进入一个任务后,必须有消费者进行消费才可以继续put,如果没有消费者, 则这里就会堵塞在这里。

  • 代码演示


/**
 * 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程
 * 线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。
 */
public class T08_CachedThreadPool {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService service = Executors.newCachedThreadPool();
        //初始化完成, 输出线程池基本信息
        System.out.println(service);
        for (int i = 0; i < 3; i++) {
            service.execute(() -> {
                try {
                    TimeUnit.MILLISECONDS.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName());
            });
        }
        //打印线程池基本信息, 查看线程数,  活跃线程数, 等待队列任务数, 完成的队列任务数
        System.out.println(service);

        TimeUnit.SECONDS.sleep(800);

        System.out.println(service);


    }
}
  • 输出结果

    java.util.concurrent.ThreadPoolExecutor@60e53b93[Running, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]
    java.util.concurrent.ThreadPoolExecutor@60e53b93[Running, pool size = 3, active threads = 3, queued tasks = 0, completed tasks = 0]
    pool-1-thread-1
    pool-1-thread-2
    pool-1-thread-3

4. ScheduledThreadPool

多线程---线程池的使用_第5张图片

5. WorkStealingPool

/**
 * WorkStealingPool每个线程都有自己单独的队列, 每个线程执行完自己的任务后,
 * 会去其他线程的等待队列里取出待执行的任务,去执行
 */
public class T11_WorkStealingPool {
    public static void main(String[] args) throws IOException {
        ExecutorService service = Executors.newWorkStealingPool();
        System.out.println(Runtime.getRuntime().availableProcessors());

        service.execute(new R(1000));
        service.execute(new R(2000));
        service.execute(new R(2000));
        service.execute(new R(2000)); //daemon
        service.execute(new R(2000));

        //由于产生的是精灵线程(守护线程、后台线程),主线程不阻塞的话,看不到输出
        System.in.read();
    }

    static class R implements Runnable {

        int time;

        R(int t) {
            this.time = t;
        }

        @Override
        public void run() {

            try {
                TimeUnit.MILLISECONDS.sleep(time);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(time + " " + Thread.currentThread().getName());

        }

    }
}
  • 输出结果

    8
    1000 ForkJoinPool-1-worker-1
    2000 ForkJoinPool-1-worker-3
    2000 ForkJoinPool-1-worker-5
    2000 ForkJoinPool-1-worker-4
    2000 ForkJoinPool-1-worker-2

6. ForkJoinPool

把大任务切分成一个个小任务来执行


/**
 * 把大任务切分成一个个小任务来执行
 */
public class T12_ForkJoinPool {
    static int[] nums = new int[1000000];
    //设置最小任务片为5万个数
    static final int MAX_NUM = 50000;
    static Random r = new Random();

    static {
        for (int i = 0; i < nums.length; i++) {
            nums[i] = r.nextInt(100);
        }

        System.out.println("使用Arrays.stream计算的和: " + Arrays.stream(nums).sum()); //stream api
    }


    /**
     * 没有返回值的ForkJoin
     */
    static class AddTask extends RecursiveAction {

        int start, end;

        AddTask(int s, int e) {
            start = s;
            end = e;
        }

        @Override
        protected void compute() {

            if (end - start <= MAX_NUM) {
                long sum = 0L;
                for (int i = start; i < end; i++) {
                    sum += nums[i];
                }
                System.out.println("from:" + start + " to:" + end + " = " + sum);
            } else {

                int middle = start + (end - start) / 2;

                AddTask subTask1 = new AddTask(start, middle);
                AddTask subTask2 = new AddTask(middle, end);
                subTask1.fork();
                subTask2.fork();
            }


        }

    }

    /**
     * 有返回值的ForkJoin
     */
    static class AddTaskReturn extends RecursiveTask<Long> {

        private static final long serialVersionUID = 1L;
        int start, end;

        AddTaskReturn(int s, int e) {
            start = s;
            end = e;
        }

        @Override
        protected Long compute() {

            if (end - start <= MAX_NUM) {
                long sum = 0L;
                for (int i = start; i < end; i++) {
                    sum += nums[i];
                }
                return sum;
            }

            //取中间数
            int middle = start + (end - start) / 2;

            AddTaskReturn subTask1 = new AddTaskReturn(start, middle);
            AddTaskReturn subTask2 = new AddTaskReturn(middle, end);
            subTask1.fork();
            subTask2.fork();

            return subTask1.join() + subTask2.join();
        }

    }

    public static void main(String[] args) throws IOException {
        //没有返回值
//		ForkJoinPool fjp = new ForkJoinPool();
//		AddTask task = new AddTask(0, nums.length);
//		fjp.execute(task);
//        System.in.read();

        //有返回值
        ForkJoinPool fjp = new ForkJoinPool();
        AddTaskReturn task = new AddTaskReturn(0, nums.length);
        fjp.execute(task);
        long result = task.join();
        System.out.println("使用ForkJoinPool计算出的和: "+ result);
		System.in.read();

    }
}

没有返回值的输出结果

使用Arrays.stream计算的和: 49544455

from:968750 to:1000000 = 1548248
from:218750 to:250000 = 1539443
from:718750 to:750000 = 1547722
from:468750 to:500000 = 1545115
from:843750 to:875000 = 1544120
from:937500 to:968750 = 1546731
from:187500 to:218750 = 1535385
from:437500 to:468750 = 1551864
from:687500 to:718750 = 1547479
from:812500 to:843750 = 1542819
from:406250 to:437500 = 1548120
from:593750 to:625000 = 1547728
from:656250 to:687500 = 1544024
from:156250 to:187500 = 1544398
from:93750 to:125000 = 1558824
from:375000 to:406250 = 1551525
from:906250 to:937500 = 1544222
from:625000 to:656250 = 1555051
from:562500 to:593750 = 1547992
from:875000 to:906250 = 1548874
from:31250 to:62500 = 1541202
from:343750 to:375000 = 1541366
from:62500 to:93750 = 1550987
from:281250 to:312500 = 1539370
from:312500 to:343750 = 1542633
from:250000 to:281250 = 1538104
from:125000 to:156250 = 1543367
from:0 to:31250 = 1543700
from:500000 to:531250 = 1548381
from:750000 to:781250 = 1540401
from:531250 to:562500 = 1546152
from:781250 to:812500 = 1548994

有返回值

使用Arrays.stream计算的和: 49544455
使用ForkJoinPool计算出的和: 49544455

并行流处理API

ParallelStreamAPI


/**
 * 并行流处理, 底层采用ForkJoinPool实现
 */
public class ParallelStreamAPI {
    public static void main(String[] args) {
        List<Integer> nums = new ArrayList<>();
        Random r = new Random();
        for (int i = 0; i < 10000; i++) {
            nums.add(1000000 + r.nextInt(1000000));
        }
        //System.out.println(nums);

        long start = System.currentTimeMillis();
        nums.forEach(v -> isPrime(v));
        long end = System.currentTimeMillis();
        System.out.println(end - start);

        //使用parallel stream api

        start = System.currentTimeMillis();
        nums.parallelStream().forEach(T13_ParallelStreamAPI::isPrime);
        end = System.currentTimeMillis();

        System.out.println(end - start);
    }

    /**
     * 判断是不是质数
     * @param num
     * @return
     */
    static boolean isPrime(int num) {
        for (int i = 2; i <= num / 2; i++) {
            if (num % i == 0) {
                return false;
            }
        }
        return true;
    }
}

三、自定义线程池(建议)

阿里开发规范建议自定义线程池

1. ThreadPoolExecutor

/**
 * 线程池的一些参数配置和介绍
 */
public class ThreadPoolTest {


    static class Task implements Runnable {
        private int i;

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

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + " Task " + i);
            try {
                //模拟业务处理过程,业务执行完才会释放
                // 这里阻塞住执行到这里的线程, 等待手动释放
                System.in.read();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        @Override
        public String toString() {
            return "Task{" +
                    "i=" + i +
                    '}';
        }
    }

    public static void main(String[] args) {
        ThreadPoolExecutor tpe = new ThreadPoolExecutor(2, 4,
                60, TimeUnit.SECONDS,//存活60秒内没有工作做,则将线程归还给操作系统
                new ArrayBlockingQueue<Runnable>(4),//等待队列, 里面设置只能装4个任务
                Executors.defaultThreadFactory(),//创建线程的线程工厂
                new ThreadPoolExecutor.CallerRunsPolicy()); //当线程池中没有空闲线程,并且线程的等待队列也都满了之后, 提供的拒绝策略
//                new ThreadPoolExecutor.AbortPolicy());
//                new ThreadPoolExecutor.DiscardPolicy());
//                new ThreadPoolExecutor.DiscardOldestPolicy()); //扔掉那个排队时间最久的
        //jdk默认提供了四种拒绝策略, 也可以自定义
        //这四种分别如下:
        //1.AbortPolicy: 抛异常
        //2.DiscardPolicy:扔掉, 不抛异常
        //3.DiscardOldestPolicy: 扔掉排队时间最久的
        //4.CallerRunsPolicy: 调用者处理任务: 哪个线程发起的任务, 哪个线程去处理它

        for (int i = 0; i < 8; i++) {
            tpe.execute(new Task(i));
        }

        //输出等待队列, 由于设置的最大线程数为4个, 所以第一次执行完毕后,
        //等待队里里会有8-4=4个线程
        System.out.println("第一次等待队列: " + tpe.getQueue());

        //再次执行一条任务
        tpe.execute(new Task(100));

        //再次输出
        System.out.println("第二次等待队列: " + tpe.getQueue());
        tpe.shutdown();
    }
}
  • 输出结果

    pool-1-thread-2 Task 1
    pool-1-thread-4 Task 7
    pool-1-thread-3 Task 6
    pool-1-thread-1 Task 0
    第一次等待队列: [Task{i=2}, Task{i=3}, Task{i=4}, Task{i=5}]
    main Task 100
    1
    pool-1-thread-2 Task 2
    第二次等待队列: [Task{i=3}, Task{i=4}, Task{i=5}]
    1
    pool-1-thread-1 Task 3
    pool-1-thread-3 Task 4
    1
    pool-1-thread-4 Task 5
    1
    1

你可能感兴趣的:(多线程与高并发,多线程,并发编程,线程池)