多线程

实现方式

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时间所占比例越高,需要越少线程。

你可能感兴趣的:(多线程)