大家是不是对线程池的技术还是不够明白啦?相信看完后大家会有不一样的体验!!!
对于服务端的程序,进场面对的是客户端的请求,那么如果客户端传来的内容比较单一,内容比较小,需要服务端快速的处理并返回结果;如果把每次的任务服务端都来创建一个线程然后处理,这在我们传统的系统中也是可行的,但是如果面对成千上万个任务交递交到服务器时,如果我们还采用一个任务一个线程的方式,那么就会创建到成千上万的线程,这当然不是一个好的选择.因为这会使的操作系统频繁的进行线程上下文切换(如果不明白线程的上下文切换请关注上一个博客有介绍),增加了系统的负载,而线程的创建和消亡都是需要耗费系统资源,也浪费了系统资源.
那么这个时候怎么办呢?线程池技术就能很好的处理他,他预先创建了若干数量的线程,并非不能由用户直接对线程的创建进行控制,在这个前提下重复的使用固定或较为固定数目的线程来完成任务的执行.这样做的好处是,一方面,消除了频繁创建和消亡线程的系统资源开销,另一方面,面对过量的提交能够平缓的劣化.
接下来我们看看如果写一个简单的线程池接口
/**
* @author Bxf
* @function 线程池的接口定义
* @date 2018-5-10 10:13:26
* */
public interface Threadpool <Job extends Runnable> {
//执行job,这个需要实现Runnable
void execute(Job job);
//关闭线程池
void shutdown();
//增加工作者线程
void addWorkers(int num);
//减少工作者线程
void removeWorker(int num);
//得到正在执行的任务数量
int getJobSize();
}
到这里我们的线程池的接口定义好了
那他的作用呢? 客户端可以通过execute(Job job)方法将job提交到线程池,而客户端自身不用等待job的执行完成.除了execute(Job job)方法外,线程池接口提供了增大/减少工作者线程以及关闭线程池的方法.这里工作者线程代表着一个重复执行job的线程,而每个由客户端提交的job都将进入到一个工作队列中等待工作者线程的处理.
那么我们来看下实现代码
/**
* @author bxf
* @function 线程池实现
* @date 2018-5-10 11:16:17
* */
public class DefaultThreadPool <Job extends Runnable> implements Threadpool<Job> {
//线程池的最大限制
private static final int MAX_WORKER_NUMBERS = 10;
//线程池默认的数量
private static final int DEFAULT_WORKER_NUMBERS = 5;
//线程池最小的数量
private static final int MIN_WORKER_NUMBER = 1;
//工作列表 向里面插入工作任务
private final LinkedList<Job> jobs = new LinkedList<Job>();
//工作者列表
private final List<Worker> workers = Collections.synchronizedList(new ArrayList<Worker>());
//工作者线程数量
private int workerNum = DEFAULT_WORKER_NUMBERS;
//线程编号生成
private AtomicLong threadNum = new AtomicLong();
//初始化
public DefaultThreadPool() {
initalizeWorkers(DEFAULT_WORKER_NUMBERS);
}
public DefaultThreadPool(int num) {
workerNum = num > MAX_WORKER_NUMBERS ? MAX_WORKER_NUMBERS :num < MIN_WORKER_NUMBER ?MIN_WORKER_NUMBER :num;
initalizeWorkers(workerNum);
}
@Override
public void execute(Job job) {
if(job != null) {
synchronized (jobs) {
jobs.addLast(job);
jobs.notify();
}
}
}
@Override
public void shutdown() {
for(Worker worker :workers) {
worker.shutdown();
}
}
@Override
public void addWorkers(int num) {
synchronized (jobs) {
//限制不能超过最大值
if(num + this.workerNum > MAX_WORKER_NUMBERS) {
num = MAX_WORKER_NUMBERS - this.workerNum;
}
initalizeWorkers(num);
this.workerNum += num;
}
}
@Override
public void removeWorker(int num) {
synchronized (jobs) {
if(num >= this.workerNum) {
throw new IllegalArgumentException("beyond workNum");
}
//按照给定的数量停止Worker
int count = 0;
while(count < num) {
Worker worker = workers.get(count);
if(workers.remove(worker)) {
worker.shutdown();
count++;
}
}
this.workerNum -= count;
}
}
@Override
public int getJobSize() {
return jobs.size();
}
//初始化
private void initalizeWorkers(int num) {
for(int i = 0;i < num;i++ ) {
Worker worker = new Worker();
workers.add(worker);
Thread thread = new Thread(worker,""+threadNum.incrementAndGet());
thread.start();
}
}
//工作者消费任务
class Worker implements Runnable{
//是否工作
private volatile boolean runing = true;
@Override
public void run() {
while(runing) {
Job job = null;
synchronized (jobs) {
//如果工作者列表是空,那么wait
while(jobs.isEmpty()) {
try{
jobs.wait();
}catch(InterruptedException ex) {
//知道外部对workerThread的中断操作,返回
Thread.currentThread().interrupt();
return;
}
}
//取出job
job = jobs.removeFirst();
}
if(job != null){
try {
job.run();
}catch(Exception ex) {
System.out.println("执行异常");
}
}
}
}
public void shutdown() {
runing = false;
}
}
}
从线程池的实现可以看到,当客户端调用excute(job)方法时,会不断的向任务列表jsos中添加job,而每个工作者线程会不断地从jobs上取出一个job进行执行,当jobs为空时,工作线程进入等待状态.
添加一个job后,对工作队列jobs调用了其notify()方法,而不是notifyAll方法,因为能够确定有工作者线程被唤醒,这时候使用notify()方法会比notifyAll方法获得更小的开销(避免将等待队列中的线程全部移动到阻塞队列中).
可以看到,线程池的本质就是使用了一个线程安全得工作队列连接工作者线程和客户端线程,客户端线程将任务放入工作队列后便返回,而工作者线程则不断的从工作队列上取出工作并执行.当工作队列为空时,所有的工作者线程均等待在工作队列上,当有客户端提交了一个任务之后会通知任意一个工作者线程,随着大量的任务被提交,更多的工作者线程会被唤醒.
好啦就介绍到这里,后续会慢慢研究更多的只是和大家分享!!!!
如有不足请大家指点!!!