什么是线程池

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了什么是线程池相关的知识,希望对你有一定的参考价值。

1、线程池简介:

多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力。    

假设一个服务器完成一项任务所需时间为:T1 创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间。


如果:T1 + T3 远大于 T2,则可以采用线程池,以提高服务器性能。

一个线程池包括以下四个基本组成部分:

1、线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;

2、工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;

3、任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;

4、任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。

线程池技术正是关注如何缩短或调整T1,T3时间的技术,从而提高服务器程序性能的。它把T1,T3分别安排在服务器程序的启动和结束的时间段或者一些空闲的时间段,这样在服务器程序处理客户请求时,不会有T1,T3的开销了。

线程池不仅调整T1,T3产生的时间段,而且它还显著减少了创建线程的数目,看一个例子:

假设一个服务器一天要处理50000个请求,并且每个请求需要一个单独的线程完成。在线程池中,线程数一般是固定的,所以产生线程总数不会超过线程池中线程的数目,而如果服务器不利用线程池来处理这些请求则线程总数为50000。一般线程池大小是远小于50000。所以利用线程池的服务器程序不会为了创建50000而在处理请求时浪费时间,从而提高效率。


代码实现中并没有实现任务接口,而是把Runnable对象加入到线程池管理器(ThreadPool),然后剩下的事情就由线程池管理器(ThreadPool)来完成了



[java] view plaincopy

package mine.util.thread;  

import java.util.LinkedList;  

import java.util.List;  

/** 

* 线程池类,线程管理器:创建线程,执行任务,销毁线程,获取线程基本信息 

*/  

public final class ThreadPool  

// 线程池中默认线程的个数为5  

private static int worker_num = 5;  

// 工作线程  

private WorkThread[] workThrads;  

// 未处理的任务  

private static volatile int finished_task = 0;  

// 任务队列,作为一个缓冲,List线程不安全  

private List<Runnable> taskQueue = new LinkedList<Runnable>();  

private static ThreadPool threadPool;  

// 创建具有默认线程个数的线程池  

private ThreadPool()  

this(5);  

 

// 创建线程池,worker_num为线程池中工作线程的个数  

private ThreadPool(int worker_num)  

ThreadPool.worker_num = worker_num;  

workThrads = new WorkThread[worker_num];  

for (int i = 0; i < worker_num; i++)  

workThrads[i] = new WorkThread();  

workThrads[i].start();// 开启线程池中的线程  

 

 

// 单态模式,获得一个默认线程个数的线程池  

public static ThreadPool getThreadPool()  

return getThreadPool(ThreadPool.worker_num);  

 

// 单态模式,获得一个指定线程个数的线程池,worker_num(>0)为线程池中工作线程的个数  

// worker_num<=0创建默认的工作线程个数  

public static ThreadPool getThreadPool(int worker_num1)  

if (worker_num1 <= 0)  

worker_num1 = ThreadPool.worker_num;  

if (threadPool == null)  

threadPool = new ThreadPool(worker_num1);  

return threadPool;  

 

// 执行任务,其实只是把任务加入任务队列,什么时候执行有线程池管理器觉定  

public void execute(Runnable task)  

synchronized (taskQueue)  

taskQueue.add(task);  

taskQueue.notify();  

 

 

// 批量执行任务,其实只是把任务加入任务队列,什么时候执行有线程池管理器觉定  

public void execute(Runnable[] task)  

synchronized (taskQueue)  

for (Runnable t : task)  

taskQueue.add(t);  

taskQueue.notify();  

 

 

// 批量执行任务,其实只是把任务加入任务队列,什么时候执行有线程池管理器觉定  

public void execute(List<Runnable> task)  

synchronized (taskQueue)  

for (Runnable t : task)  

taskQueue.add(t);  

taskQueue.notify();  

 

 

// 销毁线程池,该方法保证在所有任务都完成的情况下才销毁所有线程,否则等待任务完成才销毁  

public void destroy()  

while (!taskQueue.isEmpty()) // 如果还有任务没执行完成,就先睡会吧  

try  

Thread.sleep(10);  

catch (InterruptedException e)  

e.printStackTrace();  

 

 

// 工作线程停止工作,且置为null  

for (int i = 0; i < worker_num; i++)  

workThrads[i].stopWorker();  

workThrads[i] = null;  

 

threadPool=null;  

taskQueue.clear();// 清空任务队列  

 

// 返回工作线程的个数  

public int getWorkThreadNumber()  

return worker_num;  

 

// 返回已完成任务的个数,这里的已完成是只出了任务队列的任务个数,可能该任务并没有实际执行完成  

public int getFinishedTasknumber()  

return finished_task;  

 

// 返回任务队列的长度,即还没处理的任务个数  

public int getWaitTasknumber()  

return taskQueue.size();  

 

// 覆盖toString方法,返回线程池信息:工作线程个数和已完成任务个数  

@Override  

public String toString()  

return "WorkThread number:" + worker_num + "  finished task number:"  

+ finished_task + "  wait task number:" + getWaitTasknumber();  

 

/** 

* 内部类,工作线程 

*/  

private class WorkThread extends Thread  

// 该工作线程是否有效,用于结束该工作线程  

private boolean isRunning = true;  

/* 

* 关键所在啊,如果任务队列不空,则取出任务执行,若任务队列空,则等待 

*/  

@Override  

public void run()  

Runnable r = null;  

while (isRunning) // 注意,若线程无效则自然结束run方法,该线程就没用了  

synchronized (taskQueue)  

while (isRunning && taskQueue.isEmpty()) // 队列为空  

try  

taskQueue.wait(20);  

catch (InterruptedException e)  

e.printStackTrace();  

 

 

if (!taskQueue.isEmpty())  

r = taskQueue.remove(0);// 取出任务  

 

if (r != null)  

r.run();// 执行任务  

 

finished_task++;  

r = null;  

 

 

// 停止工作,让该线程自然执行完run方法,自然结束  

public void stopWorker()  

isRunning = false;  

 

 

 


测试代码:


[java] view plaincopy

package mine.util.thread;  

//测试线程池  

public class TestThreadPool  

public static void main(String[] args)  

// 创建3个线程的线程池  

ThreadPool t = ThreadPool.getThreadPool(3);  

t.execute(new Runnable[] new Task(), new Task(), new Task() );  

t.execute(new Runnable[] new Task(), new Task(), new Task() );  

System.out.println(t);  

t.destroy();// 所有线程都执行完成才destory  

System.out.println(t);  

 

// 任务类  

static class Task implements Runnable  

private static volatile int i = 1;  

@Override  

public void run() // 执行任务  

System.out.println("任务 " + (i++) + " 完成");  

 

 

 



运行结果:


WorkThread number:3  finished task number:0  wait task number:6

任务 1 完成

任务 2 完成

任务 3 完成

任务 4 完成

任务 5 完成

任务 6 完成

WorkThread number:3  finished task number:6  wait task number:0


分析:由于并没有任务接口,传入的可以是自定义的任何任务,所以线程池并不能准确的判断该任务是否真正的已经完成(真正完成该任务是这个任务的run方法执行完毕),只能知道该任务已经出了任务队列,正在执行或者已经完成。



你好,本题已解答,如果满意

请点右下角“采纳答案”。

http://blog.csdn.net/hsuxu/article/details/8985931

参考技术A 一、线程池的作用:
线程池作用就是限制系统中执行线程的数量。
根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了造成系统拥挤效率不高。用线程池控制线程数量,其他线程排队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了;否则进入等待队列。

二、如何使用:
要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在Executors类里面提供了一些静态工厂,生成一些常用的线程池。
1. newSingleThreadExecutor创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
2.newFixedThreadPool创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
3. newCachedThreadPool创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。4.newScheduledThreadPool创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

三、为什么要用线程池:
1.减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。2.可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。

面试官:不会真有人不知道什么是线程池吧?

1.什么是线程池

线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位,我们的程序最终都是由线程进行运作。在Java中,创建和销毁线程的动作是很消耗资源的,因此就出现了所谓“池化资源”技术。线程池是池化资源技术的一个应用,所谓线程池,顾名思义就是预先按某个规定创建若干个可执行线程放入一个容器中(线程池),需要使用的时候从线程池中去取,用完之后不销毁而是放回去,从而减少了线程创建和销毁的次数,达到节约资源的目的。

2.为什么要使用线程池

2.1 降低资源消耗

前面已经讲到线程池的出现减少了线程创建和销毁的次数,每个线程都可以被重复利用,可执行多个任务。

2.2 提高系统的响应速度

每当有任务到来时,直接复用线程池中的线程,而不需要等待新线程的创建,这个动作可以带来响应速度的提升

2.3 防止过多的线程搞坏系统

可以根据系统的承受能力,调整线程池中的工作线程的数量,防止因为线程过多服务器变慢或死机。java一个线程默认占用空间为1M,可以想象一旦手动创建线程过多极有可能导致内存溢出。

3.线程池主要参数

我们可以用Executors类来创建一些常用的线程池,但是像阿里是禁止直接通过Executors类直接创建线程池的,具体的原因稍后再谈。

在了解Executors类所提供的几个线程池前,我们首先来了解一下ThreadPoolExecutor的主要参数,ThreadPoolExecutor是创建线程池的类,我们选取参数最多的构造方法来看一下:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

3.1 corePoolSize

当向线程池提交一个任务时,如果线程池中已创建的线程数量小于corePoolSIze,即便存在空闲线程,也会创建一个新线程来执行任务,直到创建的线程数大于或等于corePoolSIze。

3.2 maximumPoolSize

线程池所允许的最大线程个数,当队列满了且已经创建的线程数小于maximumPoolSize时,会创建新的线程执行任务。

3.3 keepAliveTime

当线程中的线程数大于corePoolSIze时,如果线程空闲时间大于keepAliveTime,该线程就会被销毁。

3.4 unit

keepAliveTime的时间单位

3.5 workQueue

用于保存等待执行任务的队列

3.6 threadFactory

用于创建新线程。threadFactory创建的线程也是采用new Thread()方式,threadFactory创建的线程名都具有统一的风格:pool-m-thread-n

3.7 handler

拒绝策略,当线程池和队列满了之后,再加入新线程后会执行此策略。

下面是四种线程池的拒绝策略:

AbortPolicy:中断任务并抛出异常

DiscardPolicy:中段任务但是不抛出异常

DiscardOldestPolicy:丢弃队列中最老的任务,然后尝试提交新任务

CallerRunsPolicy:由调用线程处理该任务

4.线程池执行流程

当我们了解了ThreadPoolExecutor的七个参数后,我们就可以很快的理解线程池的流程:

当提交任务后,首先判断当前线程数是否超过核心线程数,如果没超过则创建新线程执行任务,否则判断工作队列是否已满,如果未满则将任务添加到队列中,否则判断线程数是否超过最大线程数,如果未超过则创建线程执行任务,否则执行拒绝策略。

5.Executors提供的线程池

executors提供了许多种线程池供用户使用,虽然很多公司禁止使用executors创建线程池,但是对于刚开始解除线程池的人来说,Executors类所提供的线程池能很好的带你进入多线程的世界。

5.1 newSingleThreadExecutor

ExecutorService executorService = Executors.newSingleThreadExecutor();

听名字就可以知道这是一个单线程的线程池,在这个线程池中只有一个线程在工作,相当于单线程执行所有任务,此线程可以保证所有任务的执行顺序按照提交顺序执行,看构造方法也可以看出,corePoolSize和maximumPoolSize都是1。

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

5.2 newFixedThreadPool

ExecutorService executorService = Executors.newFixedThreadPool(2);

固定长度的线程池,线程池的长度在创建时通过变量传入。下面是newFixedThreadPool的构造方法,corePoolSize和maximumPoolSize都是传入的参数值

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

5.3 newCachedThreadPool

ExecutorService executorService = Executors.newCachedThreadPool();

可缓存线程池,这个线程池设定keepAliveTime为60秒,并且对最大线程数量几乎不做控制。

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

观察构造方法,corePoolSize = 0,maximumPoolSize = Integer.MAX_VALUE,即线程数量几乎无限制。设定keepAliveTime 为60秒,线程空闲60秒后自动结束,因为该线程池创建无限制,不会有队列等待,所以使用SynchronousQueue同步队列。

5.4 newScheduledThreadPool

创建一个定时的线程池。此线程池支持定时以及周期性执行任务的需求。下面是newScheduledThreadPool的用法:

Thread thread1=new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"thread1");
    }
});
Thread thread2=new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"thread2");
    }
});
Thread thread3=new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"thread3");
    }
});
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
//在1000ms后执行thread1
scheduledExecutorService.schedule(thread1,1000,TimeUnit.MILLISECONDS);
//在1000ms后每隔1000ms执行一次thread2,如果任务执行时间比间隔时间长,则延迟执行
scheduledExecutorService.scheduleAtFixedRate(thread2,1000,1000,TimeUnit.MILLISECONDS);
//和第二种方式类似,但下一次任务开始的时间为:上一次任务结束时间(而不是开始时间) + delay时间
scheduledExecutorService.scheduleWithFixedDelay(thread3,1000,1000,TimeUnit.MILLISECONDS);

6.为什么阿里巴巴禁止程序员用Exectors创建线程池

如果你的idea装了Alibaba Java Codeing Guidelines插件(推荐大家使用,有助于让你的代码更加规范),那么当你写了Exectors创建线程池后会看到提示:

并且阿里将这个用法定义为Blocker,即不允许使用,而是让人们用ThreadPoolExecutor的方式创建线程池。原因是通过ThreadPoolExecutor的方式,这样的处理方式让写的人更加明确线程池的运行规则,规避资源耗尽的风险。

Executors返回的线程池对象的弊端如下:

1)FixedThreadPool和SingleThreadPool:

  允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。

2)CachedThreadPool:

  允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。

下面是ThreadPoolExecutor创建线程池的简单例子

int corePoolSize=5;
int maximumPoolSize=10;
long keepAliveTime=30;
BlockingQueue<Runnable> blockingQueue=new ArrayBlockingQueue(2);
RejectedExecutionHandler handler=new ThreadPoolExecutor.AbortPolicy();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.MILLISECONDS, blockingQueue, handler);
threadPoolExecutor.execute(thread1);

以上是关于什么是线程池的主要内容,如果未能解决你的问题,请参考以下文章

Java-线程池专题(什么是线程池,如何使用,为什么要用)

Java-线程池专题(什么是线程池,如何使用,为什么要用)

线程池是什么?Java四种线程池的使用介绍

什么叫线程池?线程池如何使用?

什么是线程池? 为什么要使用它?

什么是线程池? 为什么要使用它?