在当前实际开发中,我们已经很少见到能单独使用Thread或Runable的场景。更多的是用线程池。线程池即会初始化创建一些线程,这些线程当任务来临的时候,会立即去处理任务,在任务完成后也不会进行销毁,而是等待下一个任务的到来。
1.为什么要使用线程池?
(1)线程的创建和销毁都需要时间,那么理论上我们创建线程执行任务,包含的时间是 创建的时间T1、执行任务的时间 T2、销毁的时间T3。所以执行一个任务的时间成本是T1+T2+T3。线程池的线程只包含了执行了时间。理论上是T2。
(2)线程的频繁创建和杂乱无章的分布,对线程的管理性极差,极易造成CPU的浪费。
(3)线程池会降低资源的消耗,降低创建和销毁的资源消耗。
2.实现简易版线程池
在写线程池前,要捋清Thread和Runable的关系。Runable只是给你接口让你实现工作线程的工作事务,然后附加到你new thread的线程上,其本身并不会创建线程
线程池
package ThreadPool;
import java.util.concurrent.ArrayBlockingQueue;
public class MyThreadPool {
//初始化线程的数量
private Integer InitThreads;
//阻塞队列的边界值
private Integer QueueCount;
//初始阻塞队列
private ArrayBlockingQueue<Runnable> tasks;
//初始工作线程队列
private WorkThread[] workThreads;
//运行用户自行赋值
public MyThreadPool(Integer InitThreads,Integer QueueCount){
if (InitThreads<=0){
InitThreads=5;
}
if (QueueCount<=0){
QueueCount=100;
}
tasks=new ArrayBlockingQueue<Runnable>(QueueCount);
workThreads=new WorkThread[InitThreads];
//并且在线程中初始化线程并启动
for (int i=0;i<workThreads.length;i++){
workThreads[i]=new WorkThread();
workThreads[i].start();
}
}
//任务进入线程池的方法
public void putTask(Runnable rs){
tasks.add(rs);
}
//线程池销毁的方法
public void destroy(){
//让工作池中的每个线程都进行中断操作
for (int i=0;i<workThreads.length;i++){
workThreads[i].interrupt();
}
}
//工作线程
class WorkThread extends Thread{
public void run(){
Runnable rs=null;
//如果此线程没有被阻塞,那么就要去完成队列的任务,一直到队列任务都被完成为止
while (!isInterrupted()){
rs=tasks.poll();
if (rs!=null) {
rs.run();
System.out.println(rs+"is start run");
}
}
}
}
}
任务Runnable
package ThreadPool;
public class MyRunnable implements Runnable {
private String name;
public MyRunnable(String name){
this.name=name;
}
@Override
public void run() {
try {
System.out.println(name+" 被放入任务队列中...");
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Main测试类
package ThreadPool;
public class Main {
public static void main(String[] args) {
//实例化连接池
MyThreadPool pool=new MyThreadPool(5,20);
for (int i=0;i<10;i++){
pool.putTask(new MyRunnable("Task"+i));
}
}
}
运行结果:
在简易版线程池中,面临最大的问题是,不能根据任务的数量,启动的线程进行自适应的调节,避免大量线程在空启动。没有任务给它执行。造成了资源的浪费。
3.线程池和连接池的不同?
在学线程池前,我们肯定了解过连接池,连接池的设计思想就是,我们初始化一些连接,如果有需要连接的用户,就会去连接池取出连接,用完后再还回去。但这种思想在线程池中肯定不行,为什么呢?因为线程一旦运行完就结束,取出来已启动好的进行运行,当这个任务执行完,线程也就结束了,这样池的核心思想也就实现不了。
那么它们之间有什么区别呢?
1.连接池是每次需要时候的时候,从池里取出一个连接给我们,我们再使用这个连接来交换数据,而线程池并不是在使用的时候从池里取出一个线程对象给我们使用,而是将我们的任务交给线程池,由线程池自己调度任务决定什么时候执行这个任务。
2.连接池的连接用完之后,会直接放到池内,等待下一个请求连接,而线程池的线程一旦运行完,线程也就结束了,因此不存在放回池内的操作。
3.一个连接池要持有一定数量的连接,只要保证持有这些未关闭的连接的对象即可,而线程池要持有一定数量的线程,必须保证持有的线程的run方法不会运行结束。
总结来说也就是,会有一个阻塞队列作为中间件,线程池会有一些初始化好的线程,任务首先进入阻塞队列,当线程池还有空闲的线程,池会从阻塞队列取出任务进行执行,当任务数大于线程数,这时候会设定一个范围进行扩容初始线程。
4.JDK中线程池是如何工作的?
1.线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行它们。
2.当调用 execute() 方法添加一个任务时,线程池会做如下判断:
(1)如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;
(2)如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列。
(3)如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么还是要创建线程运行这个任务;
(4)如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会抛出异常,告诉调用者“我不能再接受任务了”。
3.当一个线程完成任务时,它会从队列中取下一个任务来执行。
4.当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。
提交任务:
execute(Runable command); 没有返回值
Future submit(Callable task); 有返回值
关闭线程池:
shutdown():设置线程池的状态为关闭,只会中断没有执行任务的线程。
shutdownNow():设置线程池的状态关闭,还会尝试停止正在运行或者暂停运行的线程。