【八】Java多线程之线程池(Executors提供的4中线程池都有安全隐患、ThreadPoolExecutor总结)

零、线程池的工作原理

1.当有任务来的时候,如果没到达core核心线程数,则创建线程

2.当有任务来的时候,如果已达core核心线程数,则把任务放入队列中

3.当有任务来的时候,如果已达core核心线程数,且队列已满,但已有线程数>最大线程数,则创建线程

4.当有任务来的时候,如果已达core核心线程数,且队列已满,但已有线程数=最大线程数,则执行拒绝策略

一、ThreadPoolExecutor类

java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类。

ThreadPoolExecutor继承了AbstractExecutorService类

1.构造方法4个,及其入参解释

   public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

下面解释下一下构造器中各个参数的含义:

1-1.corePoolSize

线程池中会保持corePoolSize个线程数,即是它们是空闲的也不回收。

1-2.maximumPoolSize

线程池允许最大的线程数,它表示在线程池中最多能创建多少个线程。

1-3.keepAliveTime

如果线程池中现存线程数目超过corePoolSize,闲置线程最多等待keepAliveTime的时间就会被终止。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;

1-4.unit

参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:

TimeUnit.DAYS;               //天
TimeUnit.HOURS;             //小时
TimeUnit.MINUTES;           //分钟
TimeUnit.SECONDS;           //秒
TimeUnit.MILLISECONDS;      //毫秒
TimeUnit.MICROSECONDS;      //微妙
TimeUnit.NANOSECONDS;       //纳秒

1-5.workQueue

一个阻塞队列,用来存储等待执行的任务,这里的阻塞队列有以下几种选择:

1.ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。

2.DelayQueue:DelayQueue = BlockingQueue + PriorityQueue + Delayed

3.LinkedBlockingDeque:基于链表的双端阻塞队列

4.LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,内部使用了大量的锁,性能不高,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。

5.LinkedTransferQueue:LinkedTransferQueue是 SynchronousQueue 和 LinkedBlockingQueue 的合体,性能比 LinkedBlockingQueue 更高(没有锁操作),比 SynchronousQueue能存储更多的元素。

6.PriorityBlockingQueue:一个具有优先级得无限阻塞队列。

7.SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。

1-6.threadFactory

线程工厂,指定线程池创建每个线程是怎样的,是否需要特殊化处理。如果不入这个参数,系统会自动创建。

1-7.handler

拒绝策略,线程池中线程上线满了、有界队列也满了的时候还有新的任务来时怎么办,这里做兜底补锅。这里可以自己写拒绝策略的逻辑,也可以用ThreadPoolExecutor提供的四种拒绝策略:

ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务 

2.常用的非构造方法介绍

2-1.execute()

是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。

2-2.submit()

是在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果。

2-3shutdown()和shutdownNow()

是用来关闭线程池的。

shutdown的原理是只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。

shutdownNow的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。shutdownNow会首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表。

只要调用了这两个关闭方法的其中一个,isShutdown方法就会返回true。

当所有的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminaed方法会返回true。

至于我们应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定。

通常调用shutdown来关闭线程池,如果任务不一定要执行完,则可以调用shutdownNow。

还有很多其他的方法:

比如:getQueue() 、getPoolSize() 、getActiveCount()、getCompletedTaskCount()等获取与线程池相关属性的方法

3.代码示例

package com.sid;

import java.util.concurrent.*;

/**
 * @program: thread-test
 * @description:
 * @author: Sid
 * @date: 2019-01-15 17:02
 * @since: 1.0
 **/

//CPU密集型  CPU核数+1  或者 CPU核数*2
//IO密集型  CPU核数/(1-阻塞系数) 阻塞系数一般是0.8-0.9之间
//不是随便哪个人想建一个线程池就建一个,一般是在Util中建,统一管理,提供一个线程池工厂,根据需求创建不同的线程池,基础架构不封装线程池
//用javaBean创建线程池,在destroyMaserd里面销毁的时候调用shutdown.或者自己写个holk,把线程池取出来调用shutdown方法,服务关闭的时候要关线程池
public class ThreadPoolExecutorTest {
    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(5,
                Runtime.getRuntime().availableProcessors() * 2,
                60,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(200),
                //指定创建每个线程是怎样的,是否需要特殊化处理。如果不入这个参数,系统会自动创建
                new ThreadFactory() {
                    @Override
                    public Thread newThread(Runnable r) {
                        Thread t = new Thread(r);
                        //设置这个线程池创建的线程的名字,方便在日志中查看
                        t.setName("order-thread");
                        if(t.isDaemon()) {
                            //设置非守护线程  (根据使用场景)
                            t.setDaemon(false);
                        }
                        //设置线程优先级都一样,防止有人不按期望设置优先级 (根据使用场景)
                        if(Thread.NORM_PRIORITY != t.getPriority()) {
                            t.setPriority(Thread.NORM_PRIORITY);
                        }
                        return t;
                    }
                },
                //线程池中线程上线满了、有界队列也满了的时候还有新的任务来,就会使用拒绝策略,这里做兜底补锅
                new RejectedExecutionHandler() {
                    @Override
                    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                        System.err.println("拒绝策略:" + r);
                    }
                });

        pool.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println(Thread.currentThread().getName()+" is runing");
                    System.out.println(Thread.currentThread().getName()+" will sleep");
                    Thread.sleep(5000);
                    System.out.println(Thread.currentThread().getName()+" is go on");
                    System.out.println(Thread.currentThread().getName()+" is done");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        pool.shutdown();
    }
}

二、Executors提供有四种封装好了的线程池

建议使用过ThreadPoolExecutor来创建线程池。

不推荐使用Executors来创建线程池,它有安全隐患,下面每种会详细讲安全隐患在哪儿。

1.newCachedThreadPool

创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。 

不推荐理由:最大线程数无限大。

创建该线程池函数源码:

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

- corePoolSize =0
- maximumPoolSize为Integer.MAX_VALUE,是一种可以无限扩容的线程池。 
- keepAliveTime为60,超过核心线程数的线程空闲时间超过60s就会被杀死; 
- 阻塞队列采用SynchronousQueue,不存储元素、没有内部容量、阻塞。每个插入操作必须等待另一个线程的对应移除操作。支持可选的公平排序策略。SynchronousQueue的吞吐量高于LinkedBlockingQueue 和 ArrayBlockingQueue。

该线程池的工作机制则是:

来一个任务就创建一个线程去执行,直到创建到Integer.MAX_VALUE(相当于无限了)个线程,就执行拒绝策略AbortPolicy。

使用代码

package com.sid;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class CachedThreadPoolTest {
    public static void main(final String[] arguments) throws InterruptedException {
        ExecutorService executor = Executors.newCachedThreadPool();

        // Cast the object to its class type
        ThreadPoolExecutor pool = (ThreadPoolExecutor) executor;

        //Stats before tasks execution
        System.out.println("Core threads: " + pool.getCorePoolSize());
        System.out.println("Largest executions: " + pool.getLargestPoolSize());
        System.out.println("Maximum allowed threads: " + pool.getMaximumPoolSize());
        System.out.println("Current threads in pool: " + pool.getPoolSize());
        System.out.println("Currently executing threads: " + pool.getActiveCount());
        System.out.println("Total number of threads(ever scheduled): " + pool.getTaskCount());

        executor.execute(new Task());
        executor.submit(new Task());

        //Stats after tasks execution
        System.out.println("Core threads: " + pool.getCorePoolSize());
        System.out.println("Largest executions: " + pool.getLargestPoolSize());
        System.out.println("Maximum allowed threads: " + pool.getMaximumPoolSize());
        System.out.println("Current threads in pool: " + pool.getPoolSize());
        System.out.println("Currently executing threads: " + pool.getActiveCount());
        System.out.println("Total number of threads(ever scheduled): " + pool.getTaskCount());

        executor.shutdown();
    }

    static class Task implements Runnable {

        public void run() {
            try {
                Long duration = (long) (Math.random() * 5);
                System.out.println("Running Task! Thread Name: " + Thread.currentThread().getName());
                TimeUnit.SECONDS.sleep(duration);
                System.out.println("Task Completed! Thread Name: "+ Thread.currentThread().getName());
            }
            catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

结果

Core threads: 0
Largest executions: 0
Maximum allowed threads: 2147483647
Current threads in pool: 0
Currently executing threads: 0
Total number of threads(ever scheduled): 0
Core threads: 0
Largest executions: 2
Maximum allowed threads: 2147483647
Current threads in pool: 2
Currently executing threads: 2
Total number of threads(ever scheduled): 2
Running Task! Thread Name: pool-1-thread-1
Running Task! Thread Name: pool-1-thread-2
Task Completed! Thread Name: pool-1-thread-2
Task Completed! Thread Name: pool-1-thread-1

Process finished with exit code 0

2.newFixedThreadPool

创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

不推荐理由:虽然线程池中的线程数目是有限且固定的,但是阻塞队列的最大长度为Integer.MAX_VALUE。

创建该线程池函数源码:

    public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>(),
                                      threadFactory);
    }

 创建LinkedBlockingQueue阻塞队列方法源码:

    public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }

- FixedThreadPool是一种容量固定的线程池

-最大线程数和core核心线程数一样
- 阻塞队列采用LinkedBlockingQueue,它是一种无界队列


该线程池的工作机制则是

由于最大线程数和core核心线程数一样(则keepAliveTime无效),那么每来一个任务则创建一个线程去执行,直到达到最大线程数后,再来任务就放到队列中。由于采用的无界队列,队列的长度是Integer.MAX_VALUE,拒绝策略将不会被触发。

代码

只有第一个是详细的

package com.sid;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class FixedThreadPoolTest {
    public static void main(String[] args) {
        //参数5是线程池定长
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 10; i++) {
            final int index = i;
            fixedThreadPool.execute(new Runnable() {
                public void run() {
                    try {
                        System.out.println(index+" "+System.currentTimeMillis());
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }

        fixedThreadPool.shutdown();
    }
}

结果

0 1536493672006
1 1536493672006
2 1536493672006
3 1536493672006
4 1536493672006
5 1536493677016
6 1536493677016
9 1536493677016
7 1536493677016
8 1536493677016

Process finished with exit code 0

3.newScheduledThreadPool

创建一个定长线程池,支持定时及周期性任务执行。 

不推荐理由:线程池中的最大线程数用的Integer.MAX_VALUE。阻塞队列是无界队列。

创建该线程池函数源码:

    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

    public static ScheduledExecutorService newScheduledThreadPool(
            int corePoolSize, ThreadFactory threadFactory) {
        return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
    }

    public ScheduledThreadPoolExecutor(int corePoolSize,
                                       ThreadFactory threadFactory) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue(), threadFactory);
    }

- ScheduledThreadPool接收SchduledFutureTask类型的任务,提交任务的方式有2种; 
1. scheduledAtFixedRate; 
2. scheduledWithFixedDelay; 

- SchduledFutureTask接收参数: 
time:任务开始时间 
sequenceNumber:任务序号 
period:任务执行的时间间隔 

-最大线程数为Integer.MAX_VALUE

- 阻塞队列采用DelayQueue,它是一种无界队列; 
- DelayQueue内部封装了一个PriorityQueue,它会根据time的先后排序,若time相同,则根据sequenceNumber排序; 


- 工作线程执行流程: 
1. 工作线程会从DelayQueue中取出已经到期的任务去执行; 
2. 执行结束后重新设置任务的到期时间,再次放回DelayQueue。

 

代码

只有第一个是详细的

延迟3秒执行

package com.sid;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ScheduledThreadPoolTest {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
        //延迟3秒执行
        scheduledThreadPool.schedule(new Runnable() {
            public void run() {
                System.out.println("delay 3 seconds");
            }
        }, 3, TimeUnit.SECONDS);
    }
}

表示延迟1秒后每3秒执行一次

package com.sid;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ScheduledThreadPoolTest {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
        //表示延迟1秒后每3秒执行一次。
        scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
            public void run() {
                System.out.println("delay 1 seconds, and excute every 3 seconds "+System.currentTimeMillis());
            }
        }, 1, 3, TimeUnit.SECONDS);
    }
}

4.newSingleThreadExecutor

创建一个单线程化的线程池,

它只会用唯一的工作线程来执行任务,

保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。 

不推荐理由:虽然线程池的核心线程数和最大线程数都为1,但是阻塞队列的最大长度为Integer.MAX_VALUE。

创建该线程池的函数源码:

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

创建LinkedBlockingQueue阻塞队列的函数源码:

    public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }

阻塞队列采用LinkedBlockingQueue

 

该线程池的工作机制则是

由于最大线程数和core核心线程数都是1,线程池里面只有一个线程。如果该线程被使用时,来了任务,则任务进入队列。

代码

只有第一个是详细的

package com.sid;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SingleThreadExecutorTest {
    public static void main(String[] args) {
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            singleThreadExecutor.execute(new Runnable() {
                public void run() {
                    try {
                        System.out.println(index);
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}

三、合理的配置线程池

从以下几个角度来进行分析:

  1. 任务的性质:CPU密集型任务,IO密集型任务和混合型任务。
  2. 任务的优先级:高,中和低。
  3. 任务的执行时间:长,中和短。
  4. 任务的依赖性:是否依赖其他系统资源,如数据库连接。

任务性质不同的任务可以用不同规模的线程池分开处理。

CPU密集型任务配置尽可能少的线程数量,比如 CPU核数+1 或者 CPU核数*2

IO密集型任务,最佳线程数 = CPU数量 * CPU利用率 *(线程等待时间/线程CPU时间 + 1)。

通过Runtime.getRuntime().availableProcessors()方法获得当前设备的CPU核数。

最大线程数=QPS*每次请求处理需要的时间

混合型的任务,如果可以拆分,则将其拆分成一个CPU密集型任务和一个IO密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐率要高于串行执行的吞吐率,如果这两个任务执行时间相差太大,则没必要进行分解。

优先级不同的任务可以使用优先级队列PriorityBlockingQueue来处理。它可以让优先级高的任务先得到执行,需要注意的是如果一直有优先级高的任务提交到队列里,那么优先级低的任务可能永远不能执行。

执行时间不同的任务可以交给不同规模的线程池来处理,或者也可以使用优先级队列,让执行时间短的任务先执行。

使用有界队列,不然会有安全隐患!,有界队列能增加系统的稳定性和预警能力。

四、线程池的监控

通过ThreadPoolExecutor提供的参数进行监控。ThreadPoolExecutor里有一些属性在监控线程池的时候可以使用

completedTaskCount:线程池在运行过程中已完成的任务数量。小于或等于taskCount。

largestPoolSize:线程池曾经创建过的最大线程数量。通过这个数据可以知道线程池是否满过。如等于线程池的最大大小,则表示线程池曾经满了。

getPoolSize():线程池的线程数量。如果线程池不销毁的话,池里的线程不会自动销毁,所以这个大小只增不减。

getActiveCount():获取活动的线程数。

通过扩展ThreadPoolExecutor进行监控

通过继承ThreadPoolExecutor并重写ThreadPoolExecutor的beforeExecute,afterExecute和terminated方法,可以在任务执行前,执行后和线程池关闭前干一些事情。如监控任务的平均执行时间,最大执行时间和最小执行时间等。这几个方法在线程池里是空方法。如:

protected void beforeExecute(Thread t, Runnable r) { }
protected void afterExecute(Runnable r, Throwable t) { }
protected void terminated() { }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值