java多线程学习记录--线程池(六)

1、线程池及Executor框架:

使用线程池的原因:
服务器应用程序经常出现的情况是单个任务处理的时间很短而请求数目很大,频繁的为每一个请求创建新线程,销毁线程所带来的系统开销是非常大的。线程池通过多个任务重用线程,为线程生命周期开销问题和资源不足问题提供了解决方案。

不足:用线程池构建的应用程序容易遭受并发风险,诸如同步错误和死锁,还容易遭受特定于线程池的风险攻击。


ThreadPoolExecutor构造参数

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

  • maximumPoolSize:线程最大线程数

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

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

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

  • threadFactory:线程工厂,用来创建线程

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

任务数x,cSize核心线程数,mSzie最大线程数,nWorks队列大小。
x < = cSize 只启动x个线程
x >=cSize && x < nWorks + cSize 会启动 <= cSize个线程,其他任务放到工作队列里。
x > cSize && x > nWorks + cSize
1、 x - (nWorks) <= mSize 会启动x - (nWorks) 个线程
2、 x - (nWorks) > mSize 会启动mSize个线程执行任务,其余的执行相应的拒接策略。

参数之间关系:

  • 如果当前poolsize小于corePoolSize,创建新线程执行任务

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

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

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

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

ThreadPoolExecutor状态:

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

ThreadPoolExecutor方法:

  • execute():提交任务,交给线程池执行

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

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

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

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

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

  • getPoolSize():线程池当前线程数量

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

  • prestartAllCoreThreads():通过new创建线程池时,除非调用prestartAllCoreThreads方法初始化核心线程,否则线程池中将没有线程,即使队列中存在多个任务,同样不会执行。

Executors类:

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

  • 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();
    }
  • 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();
    }
  • 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);
        
    }
  • 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();
    }

Future与Callable、FutureTask:

Callable与Runnable功能相似,Callable的call有返回值,可以返回给客户端,而Runnable没有返回值,一般Callable与FutureTask一起使用,或者通过线程池的submit方法返回相应的Future。

Future就是对于具体的Runnable或Callable任务的执行结果进行取消、查询是否完成、获取结果、设置结果操作。get会阻塞直到任务完成。

FutureTask则是一个RunnableFuture,而RunnableFuture实现了Runnable又实现了Future这两个接口。

有关java内存模型(JMM)、先行发生原则(happens-before)、指令重排
参考另一篇博客:https://blog.csdn.net/sdfgtr/article/details/90181323

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值