并发编程

并发编程系列

相比于线程池,我们可能接触new Thread更多一点,既然有了new Thread我们为什么还要使用线程池呢?

new Thread的弊端
a、每次new Thread新建对象,性能差

b、线程缺乏统一管理,可能无限制的新建线程,相互竞争,有可能占用过多系统资源导致死机或者OOM(OutOfMemory)

c、缺少更多功能,如更多执行、定期执行、线程中断

线程池的优势
a、重用存在的线程,减少对象创建、消亡的开销,性能好

 b、可有效控制最大并发线程数,提高系统资源利用率,同时可以避免过多资源竞争,避免阻塞

 c、提供定时执行、定期执行、单线程、并发数控制等功能

线程池类图

由类图可以看出,ThreadPoolExecutor类是线程池中最核心的一个类。我们来做重点分析

ThreadPoolExecutor构造参数
a、corePoolSize:核心线程数量,默认情况下(可预创建线程)线程池后线程池中的线程数为0,当有任务后当有任务后就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列中

 b、maximumPoolSize:线程最大线程数

 c、workQueue:阻塞队列,存储等待执行的任务,有三种取值,ArrayBlockQueue(基于数组的先进先出队列,创建时必须指定大小)、LinkedBlockingQueue(基于链表的先进先出队列,如果没有指定此队列大小,默认为Integer.MAX_VALUE)、SynchronousQueue(不会保存提交的任务,直接新建一个线程来执行新的任务)

 d、keepAliveTime:线程没有任务执行时最多保持多久时间终止,默认情况只有当线程池中的线程数大于corePoolSize时,keepAliveTIme才会起作用,当线程池中的线程数大于corePoolSize,如果一个线程的空闲时间达到keepAliveTime,则会终止。如果调用allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时keepAliveTime参数也会起作用,直到线程池的线程数为0

 e、unit:keepAliveTime的时间单位,有7中取值,如:TimeUnit.DAYS; 天,可具体到纳秒

 f、threadFactory:线程工厂,用来创建线程

 g、rejectHandler:当拒绝处理任务时的策略,通常有四种取值,ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常;ThreadPoolExecutor.DiscardPolicy:丢弃任务,但不抛出异常;ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,重新尝试执行任务(重复此过程);ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

参数之间的关系如下:
a、如果当前poolsize小于corePoolSize,创建新线程执行任务

b、如果当前poolsize大于corePoolsize,且等待队列未满,进入等待队列

c、如果当前poolsize大于corePoolsize且小于maximumPoolSize,且等待队列已满,创建新线程执行任务

d、如果当前poolsize大于corePoolSize且大于maximumPoolSize,且等待队列已满则用拒绝策略来处理该任务

e、线程池中的线程执行完任务后不会立刻退出,而是去检查等待队列是否有新的线程去执行,如果在keepAliveTime里等不到新任务,线程就会退出

ThreadPoolExecutor状态

图中需要注意的是shutdown()和shutdownNow()方法的区别,执行前者后,还在执行的线程会执行完再关闭,执行后者后,线程池会立刻关闭,正在执行的线程不再执行

ThreadPoolExecutor方法
a、execute():提交任务,交给线程池执行

b、submit():提交任务,能够返回执行结果 execute+Future

c、shutdown():关闭线程池,等待任务都执行完

d、shutdownNow():关闭线程池,不等待任务执行完

e、getTaskCount():线程池已执行的和未执行的任务总数

f、getCompletedTaskCount():已完成的任务数量

g、getPoolSize():线程池当前线程数量

h、getActiveCount():当前线程池正在执行任务的线程数量

Executors类
由上面的类图可以看出,Executors类为Executor,ExecutorService,ScheduledExecutorService提供了一些工具方法,并且Executors封装了ScheduledThreadPoolExecutor类和ThreadPoolExecutor类,所以我们倡导在实际使用中使用此类,Executors类提供了四个静态方法:

a、newCachedThreadPool:创建可缓存线程池,灵活回收空闲线程,如果没有可回收线程,则创建新线程

由上图可看出,newCachedThreadPool将corePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,使用SynchronousQueue,就是说有新任务就创新线程运行,线程空闲超过60秒,则销毁线程。demo代码如下:

复制代码
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();

    for (int i = 0; i < 10; i++) {
        final int index = i;
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                log.info("task:{}", index);
            }
        });
    }
    executorService.shutdown();
}

复制代码

b、newFixedThreadPool:创建定长线程池,可控制线程最大并发数,超出的线程会在队列中等待

newFixedThreadPool创建的线程池corePoolSize和maximumPoolSize值相等,线程空闲后直接销毁,使用的是LinkedBlockingQueue。demo代码如下:

复制代码
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);

    for (int i = 0; i < 10; i++) {
        final int index = i;
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                log.info("task:{}", index);
            }
        });
    }
    executorService.shutdown();
}

复制代码

c、newScheduledThreadPool:创建大小无限制线程池(最大为Integer.MAX_VALUE),支持定时及周期性任务执行

newScheduledThreadPool的特点是可以进行任务调度,最常用的方法是ScheduleAtFixedRate(基于固定时间间隔进行任务调度)和ScheduleWithFixedDelay(基于不固定时间间隔进行任务调度,主要取决于任务执行时间长短), demo代码如下:

复制代码
public static void main(String[] args) {

    ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
    executorService.schedule(new Runnable() {
        @Override
        public void run() {
            log.warn("schedule run");
        }
    },3,TimeUnit.SECONDS);

    executorService.scheduleAtFixedRate(new Runnable() {
        @Override
        public void run() {
            log.warn("schedule run");
        }
    },1,3,TimeUnit.SECONDS);
    
}

复制代码
  

d、newSingleThreadExecutor:创建一个单线程化的线程池,只会用唯一的线程来执行任务

newSingleThreadExecutor将corePoolSize和maximumPoolSize都固定为1,线程空闲时直接销毁,使用的LinkedBlockingQueue.demo代码如下:

复制代码
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();

    for (int i = 0; i < 10; i++) {
        final int index = i;
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                log.info("task:{}", index);
            }
        });
    }
    executorService.shutdown();
}

复制代码

线程池合理配置
线程池的具体配合需要按照实际情况进行调整,以下为两条参考原则,可先按此原则设置再根据系统负载,资源利用情况进行调整。

a、cpu密集型任务,需要尽量压榨cpu,参考值可以设置为ncpu+1;

b、io密集型任务,参考值可以设置为2*ncpu

本系列博客设计了并发基础、线程安全性、安全发布对象,线程安全策略,J.U.C组件,线程池,高并发扩展等方面的内容

http://www.cnblogs.com/sbrn/category/1207947.html

原文地址:https://www.cnblogs.com/Leo_wl/p/9000916.html