基于线程池技术的web服务器

基于线程池web服务器 基于线程池模拟web服务器,实现基本的http 协议get和post方法 立即下载

前言:

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

本篇博客技术总架构图:

一:线程池的好处

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根目录读取目录下的文件,然后输出对应的格式内容。实现的功能很简单,没有什么复杂的,不过我觉的这篇这篇博客能让我学习的地方就是线程池的使用方法,在处理高并发的请求时,线程池技术基本是必不可少的。

保姆级教程:用GROMACS 2022.4的拉伸模拟(SMD)加速蛋白-蛋白组装过程 本文详细介绍了如何使用GROMACS 2022.4的拉伸分子动力学模拟(SMD)技术加速大蛋白组装过程。通过CHARMM36力场和SMD技术,将传统需要数月的蛋白结合过程缩短至数天,大幅提升模拟效率。文章包含参数设计、索引组创建、周期性边界处理等实战技巧,并提供了完整的GPU加速工作流和常见问题解决方案。 阅读详情

相关推荐

细说STM32单片机SPI-Flash芯片的FatFS移植及其应用实例详解

本文以开发板上的SPI-Flash芯片W25Q16为媒介详细介绍CubeMX中FatFS各个参数的意义和设置,分析生成的代码中FatFS各个文件的作用和关联,完成针对W25Q16的硬件访问层的移植。继续使用旺宝红龙开发板STM32F407ZGT6 KITV1.0。

wenchm的博客 1646

【webserver】第2节 : 线程池介绍

线程池的前置知识 线程池的实现

qq_53157334的博客 1090

OctoMag: An Electromagnetic System for 5-DOF Wireless Micromanipulation

电磁线圈组驱动经典系统,可以作为参考。 OctoMag: 用于5自由度无线微操作的一个电磁铁系统 OctoMag: An Electromagnetic System for 5-DOF Wireless Micromanipulation [1] Paper Link Authors: J. J. Abbott, etc. 2010, TRO IEEE Transactions on Robot...

XU Yangxin的博客 1666

【Webserver】——线程池的原理,手写线程池

线程池是一种 利用 池化技术思想 来实现的线程管理技术,主要是为了复用线程、便利地管理线程和任务、并将线程的 创建和任务的执行 解耦开来。我们可以创建线程池来复用已经创建的线程来降低频繁创建和销毁线程所带来的资源消耗。

sjp11的博客 1122

技术美术个人笔记(十三)——Houdini金属弯曲模拟

最近接收到使用Houdini自动解算破损/砸坏/扭曲汽车的需求,以此文记录制作金属弯曲自动解算工具时使用的部分节点解析;

小谁的博客 2151

AutoDock Vina实战:从零开始完成分子对接(附7VU6案例详解)

本文提供了一份详细的AutoDock Vina分子对接实战指南,以SARS-CoV-2 3CL蛋白酶(PDB ID: 7VU6)为例,从零开始手把手教学。内容涵盖软件安装、环境配置、受体与配体文件准备、对接参数设置,以及结果分析与可视化解读,旨在帮助计算生物学与药物发现领域的新手快速掌握这一核心技能。

ooo22的博客 537

基于线程池的HTTP服务器(C/C++)

浏览器输入用户名密码登录注册到注册成功,返回注册成功页面底层发生了什么?想大概了解http/1.0协议底层大概如何实现的吗?这个小项目将带你了解。

我只是一个路过的老年人 1842

Material Studio 2024安装避坑指南:从证书配置到权限设置的完整解决方案

本文详细解析了Material Studio 2024的安装流程,从环境准备到权限优化的完整解决方案。针对证书配置、系统权限和计算服务等常见问题,提供了12个关键风险点的避坑指南,帮助科研工作者高效完成安装,提升材料模拟工作的效率。

moon9的博客 775

Java并发编程之基于线程池技术的简单Web服务器

      目前大多浏览器都支持多线程访问,但是如果Web服务器是单线程的话,多线程的浏览器也无用武之地,因为服务端只能一个请求一个请求的顺序处理。因此,Web服务器很有必要支持并发访问。下面通过使用线程池构造一个简单的Web服务器,这个服务器可以处理HTTP请求,并处理简单的文本和jpg格式图片内容。当客户端不断请求Socket连接,Web服务器将该连接以及请求提交线程池处理,使得Web服务器能...

样young的博客 1835

电压跟随器在ADC采样电路中的作用分析

电压跟随器在ADC采集系统中扮演关键角色,它能解决分压电路因ADC输入阻抗导致的电压误差问题。通过运放构成的电压跟随器可隔离信号源与负载,保证采样精度,尤其适用于高阻抗传感器或高速采样场景。工程应用中需注意运放带宽、轨对轨输出能力及抗干扰设计。合理使用电压跟随器能显著提升信号完整性,在复杂电子系统中成为不可或缺的"信号桥梁。

硬核科技 659

WPF实现棋盘式图片切换效果

实现效果如下: 思路:在自定义控件中放置两个Image控件,点击前后翻页按钮时对图片进行裁剪,随机打乱顺序后添加动作。 步骤: 1、MyImageControl控件 /// <summary> /// MyImageControl.xaml 的交互逻辑 /// </summary> public partial class MyImageControl : UserControl { public static r

dnazhd的博客 1125

一个基于线程池技术的简单web服务器

写一个基于多线程的web服务器,能提供展示图片的功能,代码如下: 接口 ThreadPool /** * @author chendong * @date 2019/9/7 12:54 */ public interface ThreadPool<Job extends Runnable> { // 执行一个job,这个Job需要实现Runnable void ...

cd19930508的博客 366

【Python基础】01 如何安装Python及集成环境

本文详细介绍了Python 3.13开发环境的完整搭建流程,涵盖三大操作系统(Windows/macOS/Linux)的Python安装方法、PyCharm专业IDE的配置使用,以及pip包管理工具的应用技巧。内容包括:Python 3.13各系统安装指南、PyCharm社区版与专业版的安装配置、环境验证方法、pip的基本命令与镜像源优化、虚拟环境管理,以及常见问题的解决方案。通过本教程,读者可获得一个高效完备的Python开发环境,为后续编程学习打下坚实基础。

nmdbbzcl的博客 1424

手撕-基于线程池技术的简单Web服务器

最近在看《java并发编程的艺术》这本书,听说是一本面试神书,建议反复阅读 上面有一个应用实例--基于线程池技术的简单Web服务器,手撕了一遍,发现书上的代码传图片的时候有问题

精通时间管理 322

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

基于线程池技术的简单web服务器

1.首先实现一个线程池 1.1线程池接口 package threadpool; public interface ThreadPool<Job extends Runnable> { //执行一个job void execute(Job job); //关闭线程池 void shutdown(); //增加工作者线程 void ...

皮皮狗 378

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

【JAVA并发】一个基于线程池技术的简单Web服务器

导入相关包:导入所需的Java核心和网络编程相关的类库。定义类和静态成员变量:设置端口和基本路径:启动服务器:HttpRequestHandler类:run方法:处理不同类型的资源:异常处理:关闭资源:

摸鱼的快乐你不懂 342

一个基于线程池技术的简单web 服务器---简单的demo

1、 package multiThread.art; public interface ThreadPool<HttpRequestHandler> { // 执行一个HttpRequestHandler ,这个HttpRequestHandler需要实现Runnable void execute(HttpRequestHandler httpRequestHandler); // 关闭线程池 void shutdown(); // 增加工作者线程

crazy的博客 339

线程池实战——实现Web服务器

基于该技术的简单 Web 服务器实战示例,深入解读线程池原理与应用,旨在为理解线程池内部机制及在 Web 场景下的应用提供全面参考。本文聚焦 Java 线程池中的 Worker 类,先剖析其不直接使用 Thread 类的缘由,详述核心结构、执行流程,总结关键点并阐释设计优势。继而引入线程池技术。

JIGUANGDI 1709

java 服务器 线程池_带线程池的小服务器-Java实现

前两天,利用线程池技术(ThreadPool)写了个web服务器,其性能当然无法和apache iis等相比,但基本的功能都有了,唯一欠缺的是无法解析动态页面,采用解释执行(asp模式的)效率太低,如果采用编译执行,要么自己编写一个编译器来编译整个动态页面,要么采用预编译,很复杂。。。。现在把代码拿出来晒一晒!由于只是初步的设计所以没有考虑到很多设计模式,代码在优化上很不到位,请各位高手不吝赐教。...

weixin_39915204的博客 177

HTTP高并发服务器面经总结

池化技术:指提前准备一些资源,在需要时可以重复使用这些预先准备的资源。有两个特点:提前创建和重复利用。(如线程池、连接池、内存池)线程池:类似于操作系统中的缓冲区的概念,流程:先启动若干数量的线程,并让这些线程处于睡眠状态,

weixin_42264199的博客 249

自主实现web服务器

http协议被广泛使用,从移动端,pc端浏览器,http协议无疑是打开互联网应用窗口的重要协议,http在网络应用 层中的地位不可撼动,是能准确区分前后台的重要协议。 我们的服务器程序,我们就可以把它理解为是一个通过http协议进行限制的tcp通信socket套接字编程时所做的服务器客户端的那么一个程序,我们给这个服务器程序加上了一个http协议,让它发送的数据都是http封装的报文,由于有http协议,因此,这个服务器就有了可以解析浏览器发来的请求,并依据请求返回对应的响应。 而为了让我们这个服务器程序能

famur的博客 738

Tomcat中的线程池(APR和ThreadPool)

一、容器简化了程序员自身的多线程编程。         各种Web容器,如Tomcat,Resion,Jetty等都有自己的线程池(可在配置文件中配置),所以在客户端进行请求调用的时候,程序员不用针对Client的每一次请求,都新建一个线程。而容器会自动分配线程池中的线程,提高访问速度。   二、Tomcat线程池实现: 1、使用APR的Pool技术,使用了JNI。

宏轩JAVA技术专栏 1万+

C#中的线程池的使用方法

线程池是关于管理和重用已创建的线程,从而绕过连续创建和销毁线程的昂贵开销。这种技术至关重要;启动和停止线程是资源密集型的,通过重用线程,我们可以最大程度地减少开销和资源使用。

2401_82584055的博客 1054

如何创建Java中的线程池

线程是Java的一大特性,它可以是给定的指令序列、给定的方法中定义的变量或者一些共享数据(类一级的变量)。在Java中每个线程有自己的堆栈和程序计数器(PC),其中堆栈是用来跟踪线程的上下文(上下文是当线程执行到某处时,当前的局部变量的值),而程序计数器则用来跟踪当前线程正在执行

黑菜妞妞的专栏 614
上一篇: 阿里P8技术心得:java反射机制的核心原理
下一篇: redis搭建集群并用TreeSoft管理
Java_supermanNO1
博客等级 码龄7年 1170粉丝 329原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值