前言:首先简单模拟一个场景,前端有一个输入框,有一个按钮,点击这个按钮可以实现搜索输入框中的相关的文本和图片(类似于百度、谷歌搜索).看似一个简单的功能,后端处理也不难,前端发起一个请求,后端接受到这个请求,获取前端输入的内容,然后用搜索服务查找相关的数据返回给前端。但是问题来了,可能不是一个用户在搜索,假如有一万个用户同时发起请求呢?后端如何处理?如果按照单机的 处理方式,很容易线程堵死,程序崩溃、数据库崩塌。本文来介绍一下如何通过线程池来处理前端的请求。

本篇博客的目录

一:线程池的优点

二:定义一个线程池

三:  线程池实现类

四:执行任务

五:总结

本篇博客技术总架构图:

一:线程池的好处

  1.1:好处

1.1.1  线程池可以异步的执行任务,当任务进来的时候线程池首先会判断当前是否有存活可用的线程,如果有的话,线程会执行这个任务。但是任务此时可以立刻返回,并不一定必须等待任务执行完毕才会返回。假如是同步阻塞的话,当一个线程遇到Exception的时候,假如这个线程没有得到处理,那么就会造成线程堵塞,资源囤积,最终的结果只能是cpu资源耗尽,所有的任务无法处理。之前我们的线上就出现了很多dubbo服务访问超时问题,最后发现就是cpu资源耗尽,报了一个unable to create new Thread,这样就无法处理任务(最后我们进行了物理扩容并且合理限定了线程池的最大线程数量才解决这个问题)

1.1.2:线程池可以集中管理线程,可以控制线程的运行周期,这里包括动态添加线程或者移除线程。有一个很重要的点是这样的:线程的上下文切换是非常消耗性能的;假如来了一个任务,线程执行一次,然后立刻销毁;再来一个任务,再创建一个任务,用完再销毁这个线程。那么为什么不能对这个线程进行复用呢?

1.1.3:线程池的优势只有在高请求量才会体现出来,如果请求量比较好,需要处理的任务很少,那么使用线程池的作用并不明显。但是并不是线程数量越多越好,具体的数量需要评估每个任务的处理时间以及当前计算机的处理能力和数量,这个是有具体的数据体现的,我们来看一下实际数据比较:

二:定义一个线程池

 2.1:首先我们来定义一个线程池的接口,其中包含线程池开始任务、关闭线程池,增加线程、减少线程,线程池的使命就是管理线程的生命周期,包括加、减少和删除线程,还有让线程开始执行任务,自身的关闭和开启!

public interface ThreadPool<Job extends Runnable> {
/**
* 线程池开始
*/
void execute(Job job);
/**
* 关闭线程池
*/
void shutDown();
/**
* 添加线程
* @param num
*/
void addWorkers(int num);
/**
* 减少线程
* @param num
*/
void removeWorker(int num);
/**
* 获取正在等待的线程数量
* @return
*/
int getJobSize();
}

三:线程池实现类

解释: 线程池的实现类,首先就是定义线程池的默认数量,为2*cpu核心数+1,这是比较合理的计算公式。最小数量定义为1,最大数量定义为10。还用一个LinkedList来作为工作线程的集合容器。这里为什么要用linkedList而不是ArrayList呢?因为linkedList是一个双向链表,双向链表可以实现先进先出或者后进先出等集合。然后我们定义了worker来封装具体执行任务的线程,用Job来封装要执行的任务。然后在构造方法里用initWorkers方法来初始化线程池,创建指定的默认数量的线程,指定名称(用AtomicLong:原子线程安全的)并添加到管理线程的集合workers中(这个list经过synchronizedList修饰它已经成为了一个同步的集合,所做的操作都是线程安全的)。在execute中,首先获取需要指定的任务(Job),为了保证线程安全,会锁住所有的任务集合(放心synchronized这个关键字的作用,它经过jdk1.7已经优化过了,性能消耗有质的提升)。这里为什么要锁住jobs这个集合呢,答案是:为了防止在多线程环境下,有多个job同时添到这个jobs里面,任务要一个个的执行,防止无法执行任务。接着再用addLast方法将任务添加到链表的最后一个,这里就是一个先进先出的队列(先进入的线程会优先被执行)再调用jobs的notify方法唤醒其他job。而在下面的添加线程或者移除线程的方法,都必须要锁住整个工作队列,这里为了防止,执行的时候突然发现job不见了,或者添加的时候取不到最新的job等多线程下的安全问题,并且在worker线程中增加了一个running字段,用于控制线程的运行或者停止(run方法是否执行的控制条件)

public class DefaultThreadPool<Job extends Runnable> implements ThreadPool<Job> {

    private static final int MAX_WORKER_NUMBERS = 10;

    private static final int DEFAULT_WORKERS_NUMBERS = 2 * (Runtime.getRuntime().availableProcessors()) + 1;

    private static final int MIN_WORDER_NUMBERS = 1;

    private final LinkedList<Job> jobs = new LinkedList<Job>();

    //管理工作线程的集合
private final List<Worker> workers = Collections.synchronizedList(new ArrayList<Worker>()); private int workerNum = DEFAULT_WORKERS_NUMBERS; private AtomicLong threadNum = new AtomicLong(); /**
* 线程开始运行
*/
public DefaultThreadPool() { initWorkers(DEFAULT_WORKERS_NUMBERS);
} public DefaultThreadPool(int num) { workerNum = num > MAX_WORKER_NUMBERS ? MAX_WORKER_NUMBERS : num < MIN_WORDER_NUMBERS ? MIN_WORDER_NUMBERS : num; } /**
* 初始化线程
*
* @param defaultWorkersNumbers
*/
private void initWorkers(int defaultWorkersNumbers) {
for (int i = 0; i < defaultWorkersNumbers; i++) {
Worker worker = new Worker();
workers.add(worker);
Thread thread = new Thread(worker, "ThreadPool-worker" + threadNum.incrementAndGet());
thread.start();
} } /**
* 执行任务
*
* @param job
*/
@Override
public void execute(Job job) {
if (job != null) {
synchronized (jobs) {
jobs.addLast(job);
jobs.notify();
}
}
} /**
* 关闭线程
*/
@Override
public void shutDown() {
for (Worker worker : workers) {
if (worker != null) {
worker.shutDown();
}
}
} /**
* 添加线程
*
* @param num
*/
@Override
public void addWorkers(int num) {
synchronized (jobs) {
if (num + this.workerNum > MAX_WORKER_NUMBERS) {
num = MAX_WORKER_NUMBERS - this.workerNum;
}
initWorkers(num);
this.workerNum += num;
} } /**
* 移除线程
*
* @param num
*/
@Override
public void removeWorker(int num) {
synchronized (jobs) {
if (num > workerNum) {
throw new IllegalArgumentException("much workNum");
}
int count = 0;
while (count < num) {
Worker worker = workers.get(count);
if (workers.remove(worker)) {
worker.shutDown();
count++;
}
}
this.workerNum -= count;
}
} /**
* 获取工作线程的数量
*
* @return
*/
@Override
public int getJobSize() { return jobs.size();
} /**
* 工作线程
*/
public class Worker implements Runnable { private volatile boolean running = false; @Override
public void run() {
while (running) {
Job job = null;
synchronized (jobs) {
while (jobs.isEmpty()) {
try {
jobs.wait();
} catch (InterruptedException ex) {
ex.printStackTrace();
Thread.currentThread().interrupt();
return;
}
}
job = jobs.removeFirst();
}
if (job != null) {
try {
job.run();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
} public void shutDown() {
this.running = false;
}
}
}

四:简易的web http处理线程池

4.1:定义一个类,叫做SimlpeHttpHandler,其中维护着一个叫做HttpRequestHandler的job,这个job的作用就是通过socket监听固定的端口(8080),然后通过流读取web目录中的文件,根据不同的文件格式封装打印返回

public class SimleHttpHandler {

    static ThreadPool<HttpRequestHandler> threadPool = new DefaultThreadPool<HttpRequestHandler>(1);

    public String basePath;

    private ServerSocket serverSocket;

    @Resource
private HttpRequestHandler httpRequstHandler; int port = 8080; /**
* 设置端口
*
* @param port
*/
public void setPort(int port) {
if (port > 0) {
this.port = port;
} } /**
* 设置基本路径
*
* @param basePath
*/
public void setBasePath(String basePath) {
if (basePath != null) {
boolean exist = new File(basePath).exists();
boolean directory = new File(basePath).isDirectory();
if (exist && directory) {
this.basePath = basePath;
}
}
}
/**
* 开始线程
*
* @throws Exception
*/
public void start() throws Exception {
serverSocket = new ServerSocket(port);
Socket socket = null;
while ((socket = serverSocket.accept()) != null) {
try {
threadPool.execute(new HttpRequestHandler(socket, basePath));
} catch (Exception ex) {
ex.printStackTrace();
} finally {
serverSocket.close();
}
} }
}

4.2:定义一个类叫做HttpRequestHandler实现Runnable接口,然后构造进入socket和路径,在run方法中调用具体的处理方法:我将具体的业务封装到

ServerRequestManager中,然后调用它的dealRequest方法进行具体的业务处理:
@Component
public class HttpRequestHandler implements Runnable { private Socket socket; private String basePath; @Resource
private ServerRequestManager serverRequestManager; public HttpRequestHandler(Socket socket, String basePath) {
this.basePath = basePath;
this.socket = socket;
} @Override
public void run() {
serverRequestManager.dealRequest(basePath);
}
}

4.3:服务器的具体处理逻辑,这里就是根据当前的路径用流读取路径中的文件,一旦检测到文件的后缀是.jpg或者ico,就将其输出为http的内容类型为img类型的图片,否则输出为text类型。最后用colse方法来关闭流

 */
@Component
public class ServerRequestManager { private Socket socket;
public static final String httpOK = "HTTP/1.1 200 ok";
public static final String molly = "Server:Molly";
public static final String contentType = "Content-Type:";
/**
* 处理请求
*
* @param basePath
*/
public void dealRequest(String basePath) {
String content = null;
BufferedReader br = null;
BufferedReader reader = null;
PrintWriter out = null;
InputStream in = null;
try {
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String header = reader.readLine();
String filePath = basePath + header.split(" ")[1];
out = new PrintWriter(socket.getOutputStream());
if (filePath.endsWith("jpg") || filePath.endsWith("ico")) {
in = new FileInputStream(filePath);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int index = 0;
while ((index = in.read()) != -1) {
byteArrayOutputStream.write(index);
}
byte[] array = byteArrayOutputStream.toByteArray();
out.print(httpOK);
out.print(molly);
out.println(contentType + " image/jpeg");
out.println("Content-Length" + array.length);
out.print("");
socket.getOutputStream().write(array, 0, array.length);
} else {
br = new BufferedReader(new InputStreamReader(new FileInputStream(filePath)));
out = new PrintWriter(socket.getOutputStream());
out.print(httpOK);
out.print(molly);
out.print(contentType + "text/html; Charset =UTF-8");
out.print("");
while ((content = br.readLine()) != null) {
out.print(content);
}
}
out.flush();
} catch (final Exception ex) {
ex.printStackTrace();
out.println("HTTP/1.1 500");
out.println("");
out.flush();
} finally {
close(br, in, out, socket);
}
} /**
* 关闭流
*
* @param closeables
*/
public static void close(Closeable... closeables) {
if (closeables != null) {
for (Closeable closeable : closeables) {
try {
closeable.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
} }
}

五:总结

本篇博客总结了如何开发一个简单的线程池,当然功能不够齐全,比不上jdk的线程池,没有阻塞队列和超时时间和拒绝策略等;然后会用socket监听8080端口,获取web根目录读取目录下的文件,然后输出对应的格式内容。实现的功能很简单,没有什么复杂的,不过我觉的这篇这篇博客能让我学习的地方就是线程池的使用方法,在处理高并发的请求时,线程池技术基本是必不可少的。

参考资料《java并发编程的艺术》

*假如你想学习java,或者看本篇博客有任务问题,可以添加java群:618626589

基于线程池技术的web服务器的更多相关文章

  1. 基于SmartThreadPool线程池技术实现多任务批量处理

    一.多线程技术应用场景介绍 本期同样带给大家分享的是阿笨在实际工作中遇到的真实业务场景,请跟随阿笨的视角去如何采用基于开源组件SmartThreadPool线程池技术实现多任务批量处理.在工作中您是否 ...

  2. 【Java TCP/IP Socket】基于线程池的TCP服务器(含代码)

    了解线程池 在http://blog.csdn.net/ns_code/article/details/14105457(读书笔记一:TCP Socket)这篇博文中,服务器端采用的实现方式是:一个客 ...

  3. requests模块session处理cookie 与基于线程池的数据爬取

    引入 有些时候,我们在使用爬虫程序去爬取一些用户相关信息的数据(爬取张三“人人网”个人主页数据)时,如果使用之前requests模块常规操作时,往往达不到我们想要的目的,例如: #!/usr/bin/ ...

  4. Python网络爬虫之cookie处理、验证码识别、代理ip、基于线程池的数据爬去

    本文概要 session处理cookie proxies参数设置请求代理ip 基于线程池的数据爬取 引入 有些时候,我们在使用爬虫程序去爬取一些用户相关信息的数据(爬取张三“人人网”个人主页数据)时, ...

  5. 用 ThreadPoolExecutor/ThreadPoolTaskExecutor 线程池技术提高系统吞吐量(附带线程池参数详解和使用注意事项)

    1.概述 在Java中,我们一般通过集成Thread类和实现Runnnable接口,调用线程的start()方法实现线程的启动.但如果并发的数量很多,而且每个线程都是执行很短的时间便结束了,那样频繁的 ...

  6. 基于线程池的多并发Socket程序的实现

    Socket“服务器-客户端”模型的多线程并发实现效果的大体思路是:首先,在Server端建立“链接循环”,每一个链接都开启一个“线程”,使得每一个Client端都能通过已经建立好的线程来同时与Ser ...

  7. java线程池技术

    1.线程池的实现原理?简介: 多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力.假设一个服务器完成一项任务所需时间为:T1 创建线程时间, ...

  8. Java线程池技术以及实现

    对于服务端而言,经常面对的是客户端传入的短小任务,需要服务端快速处理并返回结果.如果服务端每次接受一个客户端请求都创建一个线程然后处理请求返回数据,这在请求客户端数量少的阶段看起来是一个不错的选择,但 ...

  9. 设计模式:基于线程池的并发Visitor模式

    1.前言 第二篇设计模式的文章我们谈谈Visitor模式. 当然,不是简单的列个的demo,我们以电商网站中的购物车功能为背景,使用线程池实现并发的Visitor模式,并聊聊其中的几个关键点. 一,基 ...

随机推荐

  1. Jmeter接口测试(一) Jmeter简介

    一.Jmeter介绍 (一)Jmeter简介 Apache JMeter 是 Apache 组织的开放源代码项目,是一个纯 Java 桌面应用,用于压力测试和性能测试.它最初被设计用于 Web 应用测 ...

  2. Android手机测试-ddms&monitor-抓crash,log

    1.安装adb offline解决办法: 原因就是android 4.2以上的版本过高,sdk的adb驱动不匹配,需要升级.我原本的adb是1.0.29,升级为1.0.31,问题就解决了. 2.安装s ...

  3. 433. Number of Islands【LintCode java】

    Description Given a boolean 2D matrix, 0 is represented as the sea, 1 is represented as the island. ...

  4. 003--MySQL 数据库事务

    什么是事务? 事务是一组原子性的 SQL 查询, 或者说是一个独立的工作单元. 在事务内的语句, 要么全部执行成功, 要么全部执行失败. 事务的 ACID 性质 数据库事务拥有以下四个特性, 即 AC ...

  5. 如何使用openstack OCL

    本节首先讨论 image 删除操作,然后介绍 OpenStack CLI 的使用方法,最后讨如何 Troubleshoot. Web UI 删除 image admin 登录后,Project -&g ...

  6. php中注释有关内容

    //单行注释 /*多行注释*/ /** 文档注释 (注意 文档注释与前面的那个多行注释不同)文档注释可以和特定的程序元素相关联 例如 类 函数 常量 变量方法 问了将文档注释与元素相关联 只需要在元素 ...

  7. Python中用字符串导入module

    在Python中,无法通过字符串来导入一个module文件: import "string" # Error x = "string" import x # 不 ...

  8. Masha and Bears(翻译+思维)

    Description A family consisting of father bear, mother bear and son bear owns three cars. Father bea ...

  9. OpenCV学习笔记——疑问

    vec3b:表示每一个Vec3b对象中,可以存储3个char(字符型)数据,比如可以用这样的对象,去存储RGB图像中的一个像素点.typedef Vec<uchar, 3> Vec3b; ...

  10. POJ 1971 Parallelogram Counting

    题目链接: http://poj.org/problem?id=1971 题意: 二维空间给n个任意三点不共线的坐标,问这些点能够组成多少个不同的平行四边形. 题解: 使用的平行四边形的判断条件:对角 ...