实现方式
1. 继承Thread类
2. 实现Runnable接口
两种方法的区别:Thread其实也是实现了Runable接口,底层由JVM通过Native Code启动线程。主要的区别:
1. Java的类可以实现多个接口,不能多继承。所以Runable比Thread灵活。
2. 同样的业务逻辑可以封装在Runnable的run方法里,然后用多线程去执行,或者提交线程池执行,但是注意多线程带来的数据不一致的问题。
线程池
ThreadPoolExecutor根据corePoolSize和maxPoolSize两个参设置线程池的大小。当新任务调用execute()方法提交任务时,如果正在运行线程数小于corePoolSize, 则创建新的线程处理请求,如果正在运行的线程数等于corePoolSize, 新任务会添加到队列中,直到队列满,会开辟新的线程来处理任务,但是不超过最大线程设置maxPoolSize。当任务队列满,并且线程数等于maxPoolSize的时候,就会根据拒绝策略拒绝请求。
如果线程空闲超过keepAliveTime, 非核心线程就会被收回,若allowCoreThreadTimeOut为true,则核心线程也会被收回。默认情况核心线程不会被收回。
Executors.FixedThreadPool 默认corePoolSize = maxPoolSize, keepAliveTime=0L, 队列采用LinkedBlockingQueue。其实根据队列的不同类型,还可以实现优先级等高级功能
1. AbortPolicy
拒绝策略:抛出运行时异常RejectedExecutionException
这种策略丢弃任务,并抛出异常
2. DiscardPolicy
拒绝策略:不能执行的任务将被丢弃。这种策略什么都没做
3. DiscardOldestPolicy
拒绝策略:如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序。
该策略稍微复杂一些,在pool没有关闭的前提下首先丢掉缓存在队列中的最早的任务,然后重新尝试运行该任务
4. CallerRunsPolicy
拒绝策略:线程调用运行该任务的 execute 本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度
该策略不想放弃执行任务。但是由于池中已经没有任何资源了,那么就直接使用调用该execute的线程本身来执行。
如何确定线程池的大小
根据服务的类型,比如CPU, IO,或者混合类型来设置线程池的大小。
1. 如果是CPU密集型应用,则线程池大小设置为N+1
2. 如果是IO密集型应用,则线程池大小设置为2N+1
如果一台服务器上只部署这一个应用并且只有这一个线程池,那么这种估算或许合理
最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目
比如平均每个线程CPU运行时间为0.5s,而线程等待时间(非CPU运行时间,比如IO)为1.5s,CPU核心数为8,那么根据上面这个公式估算得到:((0.5+1.5)/0.5)*8=32。这个公式进一步转化为:
最佳线程数目 = (线程等待时间与线程CPU时间之比 + 1)* CPU数目
可以得出一个结论:线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程。
网友评论