Java并发编程-Java线程池

new Thread弊端   

每次new Thread新建对象,性能差。

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

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

线程池的好处

重用存在的线程,减少对象创建、消亡的开销,性能佳。

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

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

线程池状态

        状态说明

RUNNING

接收新任务,并且能处理阻塞队列中的任务。

SHUTDOWN

不能接收新任务,但是可以处理已经保存的任务。

STOP

不能接收新的任务,也不能处理对队列中的任务。会中断正在处理任务的线程。

TIDYING

如果所有任务终止,有效线程数为0,进入该状态。

TERMINATED

调用termninated()方法进入该状态。

线程池的作用

减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。

可以根据系统的承受能力,调整线程池中工作线程的数据,防止因为消耗过多的内存导致服务器崩溃

ThreadPoolExecutor

corePoolSize

指定了线程池中的线程数量,它的数量决定了添加的任务是开辟新的线程去执行,还是放到workQueue任务队列中去。

maximumPoolSize

指定了线程池中的最大线程数量,这个参数会根据你使用的workQueue任务队列的类型,决定线程池会开辟的最大线程数量。

keepAliveTime

默认当线程池中线程数量超过corePoolSize时,空闲的线程会在多长时间内被销毁。

unit

keepAliveTime的单位。

workQueue

任务队列,被添加到线程池中,但尚未被执行的任务。

任务队列

直接提交队列

设置为SynchronousQueue队列,SynchronousQueue是一个特殊的BlockingQueue,它没有容量,每执行一个插入操作就会阻塞,需要再执行一个删除操作才会被唤醒,反之每一个删除操作也都要等待对应的插入操作。

有界的任务队列

有界的任务队列可以使用ArrayBlockingQueue实现,使用ArrayBlockingQueue有界任务队列,若有新的任务需要执行时,线程池会创建新的线程,直到创建的线程数量达到corePoolSize时,则会将新的任务加入到等待队列中。若等待队列已满,即超过ArrayBlockingQueue初始化的容量,则继续创建线程,直到线程数量达到maximumPoolSize设置的最大线程数量,若大于maximumPoolSize,则执行拒绝策略。在这种情况下,线程数量的上限与有界任务队列的状态有直接关系,如果有界队列初始容量较大或者没有达到超负荷的状态,线程数将一直维持在corePoolSize以下,反之当任务队列已满时,则会以maximumPoolSize    为最大线程数上限。

无界的任务队列

有界任务队列可以使用LinkedBlockingQueue实现,使用无界任务队列,线程池的任务队列可以无限制的添加新的任务,而线程池创建的    最大线程数量就是你corePoolSize设置的数量,也就是说在这种情况下maximumPoolSize这个参数是无效的,哪怕你的任务队列中缓存了很多未执行的任务,当线    程池的线程数达到corePoolSize后,就不会再增加了;若后续有新的任务加入,则直接进入队列等待,当使用这种任务队列模式时,一定要注意你任务提交与处理之    间的协调与控制,不然会出现队列中的任务由于无法及时处理导致一直增长,直到最后资源耗尽的问题。

优先任务队列

优先任务队列通过PriorityBlockingQueue实现,除了第一个任务直接创建线程执行外,其他的任务都被放入了优先任务队列,按优先级进行了    重新排列执行,且线程池的线程数一直为corePoolSize。通过运行的代码我们可以看出PriorityBlockingQueue它其实是一个特殊的无界队列,它其中无论添加了多少    个任务,线程池创建的线程数也不会超过corePoolSize的数量,只不过其他队列一般是按照先进先出的规则处理任务,而PriorityBlockingQueue队列可以自定义规则    根据任务的优先级顺序先后执行。

threadFactory

线程工厂,用于创建线程,一般用默认即可;

rejectHandler

拒绝策略;当任务太多来不及处理时,如何拒绝任务;一般创建线程池时,为防止资源被耗尽,任务队列都会选择创建有界任务队列,但这种模式下如果出现任务队列已满且线程池创建的线程数达到你设置的最大线程数时,这时就需要你指定ThreadPoolExecutor的RejectedExecutionHandler参数即合理的拒绝策略,来处理线程池"超载"的情况。也可以实现该接口自定义拒绝策略。

拒绝策略

AbortPolicy策略

该策略会直接抛出异常,阻止系统正常工作;

CallerRunsPolicy策略

如果线程池的线程数量达到上限,该策略会把任务队列中的任务放在调用者线程当中运行;

DiscardOledestPolicy策略

该策略会丢弃任务队列中最老的一个任务,也就是当前任务队列中最先被添加进去的,马上要被执行的那个任务,并尝试再次提交;

DiscardPolicy策略

该策略会默默丢弃无法处理的任务,不予任何处理。当然使用此策略,业务场景中需允许任务的丢失;

        当任务提交给ThreadPiolExecutor线程池中,先检查核心线程数是否已经全部使用,如果没有,交由核心线程去执行任务,如果核心线程已经全部使用,则将任务添加到任务对列里面,如果对列已经占满,比较当前线程池中的线程数量是不是超过MaximmPoolSize,如果没有超过,则创建线程去执行,也就是说线程池最多可接受的任务数为:maximumPoolSize+对列的大小。当线程池中线程的数量大于corePoolSize时,则回收空闲线程。

线程池方法

execute()

提交任务,交给线程池执行。

submit()

提交任务,能够返回执行结果execute+furure。

shutdown()

关闭线程池,等待任务都执行完。

shutdownNow()

关闭线程池,不等待任务执行完。

getTaskCount()

线程池已执行和未执行的任务总数。

getCompletedTaskCount()

已完成的任务数量。

getPoolSize()

线程池当前的线程数量。

getActiveCount()

当前线程池中正在执行任务的线程数量。

Executors提供四种线程池

newCachedThreadPool

创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

newFixedThreadPool 

创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

newScheduledThreadPool

创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

newSingleThreadExecutor

创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

线程池使用

@Slf4j
public class ThreadPoolExample3 {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        //ExecutorService executorService = Executors.newFixedThreadPool(3);
        //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();
    }
}

调度线程池

@Slf4j
public class ThreadPoolTest {
    public static void main(String[] args) {
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
//        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);
//        executorService.shutdown();
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                log.warn("timer run");
            }
        }, new Date(), 5 * 1000);
    }
}

线程池合理配置

CPU密集型任务,就需要尽量压榨CPU,参考值可以设置为NCPU+1

IO密集型任务,参考值可以设置为2*NCPU

posted @ 2021-11-08 21:35  Older&nbspSix  阅读(40)  评论(0)    收藏  举报