进程和线程
-
进程和进程之间数据是隔离的,线程之间可以访问共享内存,访问对方的数据。
-
线程是在进程里创建的,一个进程里可以创建多个线程。
线程池
- 对于线程的新建和销毁,资源开销比较大,线程池可以用来管理线程,对于新来的线程请求,直接复用旧线程,防止新的开销
- 可以管理线程执行的先后顺序
- 可以管理线程什么时候执行,比如延后多久执行
线程池的创建离不开 ThreadExecutor,那么它是怎么维护线程的呢?
ThreadExecutor原理
基本知识
- ctl 变量
高3位表示线程池状态,低29位表示Worker数量。使用CAS机制来更新和获取。
- Worker
线程池中维护的一个集合,用于执行线程,每个Worker内部维护了一个Thread对象,Worker会不停从Runnable队列中获取Runnable,然后去执行。 Worker本身也实现了Runnable接口,所以Thread执行时候,其实就是直接执行它自己。
- 线程池的5种状态
- RUNNING
线程池会接收新的Runnable请求,并且会执行阻塞队列中的Runnable - SHUTDOWN
线程池停止接收新的Runnable请求,但是会依然执行阻塞队列中的Runnable - STOP
线程池决绝接收新的Runnable,并且不会执行阻塞队列中的Runnable,尝试中断正在执行的任务。 - TIDYING
当前所有任务被关闭了,Worker数量为0 - TERMINATED
terminated()执行结束。
流程研究
我们使用线程池,主要是调用其方法execute来执行Runnable,关于execute的具体流程是什么呢?
总结来说:
- ThreadPoolExecutor内部维护了两个集合,一个是用于处理Runnable的Worker集合,一个是等待被执行的Runnable队列。
- 对于Worker,每个Worker自身就是一个Runnable,线程池分配线程,其实就是通过分配Worker对象,通过线程启动Worker,在Worker的run方法中,不停的获取Runnable对象,执行其run方法。
- 对于execute方法,会判断当前已经运行的Worker对象是否超出定义的核心线程数,如果没超出,则新建一个Worker对象,执行Runnable,如果超出了,那么放到阻塞队列中。
相关问题
- Worker本身为Runnable的原因
Worker自己实现了Runnable接口,主要是因为Thread执行时候是直接执行一个Runnable的,执行结束,线程也就结束了。但是线程池是需要复用Thread的,那么该怎么复用呢? 那么是不是可以在Thread start后,在run方法中,依次的执行Runnable 的run方法。是不是就可以实现了呢?Worker就是这个原理,通过让自己跑起来,然后在自己的run方法中,不停的查找等待的Runnable,然后拿出去执行,这样可以保证当前Thread不会因为run执行完毕而线程结束,达到了线程的复用效果。
- 当等待队列为空了。Worker会回收置为NULL吗?
- keepAliveTime 和 allowCoreThreadTimeOut方法关系
上方两个问题需要放到一起来研究。
- 当我们调用方法 allowCoreThreadTimeOut 设置允许核心线程超时后,回收线程。这个方法会配合 实例化ThreadExecutor对象时候的入参变量keepAliveTime来使用。
当这个方法我们主动设置为TRUE后(默认是false),当等待队列为空,且当前Worker没有正在运行的Runnable,那么等待keepAliveTime时间一到,就会回收Worker,将其置为NULL,否则 则会阻塞在队列读取这里,等待队列返回下一个Runnable。
- 如果我们没有调用方法 allowCoreThreadTimeOut,那么也会等待keepAliveTime时间一到,回收Worker,但是这里就不是回收所有的worker了,而是会剩下数量为 corePoolSize 的Worker数持续运行着,等待执行Runnable,多余出来的Worker会被回收。
所以对于超出corePoolSize数量的Worker一定会被回收,对于corePoolSize数量范围内的Worker,除非我们主动调用方法 allowCoreThreadTimeOut 允许其被回收,才会触发核心线程Worker的回收机制。
- corePoolSize 和 maximumPoolSize 的区别关系
corePoolSize代表核心线程数,也就是当前同时可以有多少个线程运行。当线程数目超出了这个值后,会将多余的Runnable放入到等待队列中。
当等待队列满了后,会判断当前正在执行的线程数(Worker数)是否大于 maximumPoolSize,如果是直接抛出reject异常,表示不再接收Runnable, 如果否,那么立即新建一个Worker来执行当前Runnable。
所以,这里可以看到当线程池等待队列满了后,后续来的新Runnable请求反而可以优先被执行。
同时,当我们使用无界队列时候,maximumPoolSize 也就没有意义了。
- corePoolSize 设置多少合适
[从网上搜索的数据]一般对于IO密集型(网络请求,文件读写等),推荐是2N,对于CPU密集型(计算处理比较多),推荐是N+1,N为CPU核数
Android 中可以直接使用的有四种类型的线程池
- FixThreadPool
固定大小的线程池,corePoolSize 和 maximumPoolSize相等。对于请求的线程,先看当前是否有空闲的线程,如果有直接复用运行,如果没有那么将其放到等待队列中。 线程等待使用队列为 LinkedBlockingQueue.
- SingleThreadPool
可运行线程数大小固定为1的线程池,corePoolSize 和 maximumPoolSize 都固定为1.同一时刻只可以运行一个线程,多余Runnable进入到等待队列,使用队列为 LinkedBlockingQueue
- CachedThreadPool
无固定大小的线程池,来的Runnable可以理解创建Worker进行执行,并且超时后,可以回收Worker对象, corePoolSize为0,maximumPoolSize为Int最大值。使用队列为SynchronousQueue
- ScheduleThreadPool
可以定义Runnable执行时间的线程池,corePoolSize默认是1,可以自定义,maximumPoolSize为Int最大值。使用队列为DelayedWorkQueue
队列类型
ThreadPoolExecutor允许传入的等待队列需要实现 BlockingQueue 接口,目前系统中已经存在实现该接口的如下队列供我们使用。
下方所有队列均线程安全,使用锁ReentrantLock来保证线程安全。
- LinkedBlockingQueue
内部以Node链表保存数据,链表大小为Int最大值,相当于无上限。当添加元素时候,超出链表长度会抛出异常。获取数据Node对象时候,如果队列为空,可以阻塞直到队列非空,返回Node对象。
该队列先入先出。队列使用了ReentrantLock 锁保证了线程之间的安全性。当链表满了后,存放值也会直接阻塞,直到链表有数据释放。
- ArrayBlockingQueue
内部维护了个数组,和两个int值,分别代表取值的下标和存值的下标,当数组存满后,下次再存值会阻塞,直到数组出现空余位置,才可以插入成功。取值时候也可以设置阻塞取值,即没值时候阻塞等待。
- DelayedWorkQueue
无界队列,内部维护了一个数组,数组中元素数据结构为堆排序,以执行时间节点为准进行的堆排序。取值时候,如果数组为空,则阻塞等待。
- SynchronousQueue
该队列不会存储元素,但是提供了数据插入和取出的阻塞机制。对于插入一条数据,在插入第二条数据时候,会进行阻塞等待,直到第一条数据被取出,同理取数据时候,如果没有数据也会阻塞等待,直接有数据被插入。
线程
- 线程如何同时开始
使用CountDownLatch.在每个线程的run方法中加上CountDownLatch的await方法,然后再开始时间点调用其countDown,这样可以实现所有线程同时开始执行。
- 线程如何顺序执行
- 使用CountDownLatch来限制,但是线程数量一多,可能就得定义对应数目减一的CountDownLatch,每个CountDownLatch的容量为1.
- 使用同步锁
- 使用join