什么是进程,什么是线程
进程是资源分配的最小单位,线程是CPU调度的最小单位
一个应用程序的运行就可以被看做是一个进程,而线程是运行中的实际的任务执行者。可以说,进程中包含了多个可以同时运行的线程。
我们使用new Thread()来创建一个线程,在线程创建完之后,线程就进入了就绪(Runnable)状态,此时创建出来的线程进入抢占CPU资源的状态,当线程抢到了CPU的执行权之后,线程就进入了运行状态(Running),当该线程的任务执行完成之后或者是调用stop()方法之后,线程就进入了死亡状态。什么时候线程会进入阻塞状态呢?线程主动调用了sleep()方法时,线程会进入则阻塞状态。线程在阻塞过程结束之后,会重新进入就绪状态,重新抢夺CPU资源。
多线程:多条线程同时执行任务
并发和并行:并发就是多种事件同时进行,实际上,这几种事件,并不是同时进行的,而是交替进行的,但是由于CPU的运算速度非常快,会给我们造成一种错觉,就是在同一时间内进行了多种事情。
而并行,则是真正意义上的同时进行多种事情,这种只可以在多核CPU的基础下完成。
使用synchronized关键字加上一个锁对象来定义一段代码, 这就叫同步代码块。
下面来看线程池:
在一个应用程序中,我们需要多次使用线程,也就意味着,我们需要多次创建并销毁线程。而创建并销毁线程的过程势必会消耗内存。而在Java中,内存资源是极其宝贵的,所以,提出了线程池。
线程池的好处就是可以方便的管理线程,也可以减少内存的消耗。
那么我们如何创建一个线程池呢?
Java中提供了创建线程池的一个类:Executor
我们在创建线程池的时候一般使用它的子类ThreadPoolExecutor.
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
这是ThreadPoolExecutor的一个构造方法,这个方法决定了创建出来的线程池的各种属性。
corePoolSize是核心线程数量,核心线程即使在没有被使用的时候也不会被回收。maximumPoolSize指的是线程池中可以容纳的最大线程的数量。
keepAliveTime,就是线程池中除了核心线程之外的其他的最长可以保留的时间。因为在线程池中,除了核心线程即使在无任务的情况下也不能被清除,其余的都是有存活时间的,意思就是非核心线程可以保留的最长的空闲时间。而util,就是计算这个时间的一个单位。workQueue,就是等待队列,任务可以储存在任务队列中等待被执行,执行的是FIFIO原则(先进先出)。threadFactory,就是创建线程的线程工厂,最后一个handler,是一种拒绝策略,我们可以在任务满了之后,拒绝执行某些任务。
下面来看线程池的执行流程,向线程池提交一个任务,如果还有核心线程,就执行,如果没有了核心线程,看任务队列,如果任务队列没有满,则在任务队列等待,如果任务队列满了,就创建非核心线程,执行任务,如果创建的非核心线程达到最大值了按照策略处理无法执行的任务。
任务进来时,首先执行判断,判断是否有核心线程处于空闲状态,如果有,核心线程就先执行任务,如果核心线程没有,则判断任务队列是否已满,如果没有,就将任务保存在任务队列中,等待执行,如果满了,再看线程池最大可容纳的线程数,如果没有超出这个数量,就创建非核心线程执行任务,如果超出了,就调用handler实现拒绝策略。
handler的拒绝策略:
有四种:第一种AbortPolicy:不执行新任务,直接抛出异常,提示线程池已满
第二种DisCardPolicy:不执行新任务,也不抛出异常
第三种DisCardOldSetPolicy:将消息队列中的第一个任务替换为当前新进来的任务执行
第四种CallerRunsPolicy:直接调用execute来执行当前任务
五,四种常见的线程池:
知道了各个参数的作用后,我们开始构造符合我们期待的线程池。首先看JDK给我们预定义的几种线程池:
CachedThreadPool:该线程池中没有核心线程,非核心线程的数量为Integer.max_value,就是无限大,当有需要时创建线程来执行任务,没有需要时回收线程,适用于耗时少,任务量大的情况。
ScheduledThreadPool:周期性执行任务的线程池,按照某种特定的计划执行线程中的任务,有核心线程,但也有非核心线程,非核心线程的大小也为无限大。适用于执行周期性的任务。
SingleThreadPool:只有一个线程来执行任务,适用于有顺序的任务的应用场景。
FixedThreadPool:定长的线程池,只有核心线程,没有非核心线程,核心线程的即为最大的线程数量。
我们也可以通过向构造函数中传参的方式自定义线程池。通过自定义线程池,我们可以更好的让线程池为我们所用,更加适应我的实际场景。
线程池的大小应该怎么设置呢?
一般说来,大家认为线程池的大小经验值应该这样设置:(其中N为CPU的个数)
如果是CPU密集型应用,则线程池大小设置为N+1
如果是IO密集型应用,则线程池大小设置为2N+1
如果一台服务器上只部署这一个应用并且只有这一个线程池,那么这种估算或许合理,具体还需自行测试验证。但是,IO优化中,这样的估算公式可能更适合:最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目因为很显然,线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程。下面举个例子:比如平均每个线程CPU运行时间为0.5s,而线程等待时间(非CPU运行时间,比如IO)为1.5s,CPU核心数为8,那么根据上面这个公式估算得到:((0.5+1.5)/0.5)*8=32。这个公式进一步转化为:最佳线程数目 = (线程等待时间与线程CPU时间之比 + 1)* CPU数目刚刚说到的线程池大小的经验值,其实是这种公式的一种估算值。
参考博客:
https://blog.youkuaiyun.com/weixin_40271838/article/details/79998327
https://www.jianshu.com/p/f030aa5d7a28
https://www.zhihu.com/question/38128980
http://ifeve.com/how-to-calculate-threadpool-size/
https://blog.youkuaiyun.com/weixin_34112181/article/details/91882162