目录
线程应用实例
等待超时模式
一个简单的数据库连接池示例
线程池技术及其示例
一个基于线程池技术的简单Web服务器
线程应用实例
等待超时模式 开发人员经常会遇到这样的方法调用场景:调用一个方法时等待一段时间(一般来说是给 定一个时间段),如果该方法能够在给定的时间段之内得到结果,那么将结果立刻返回,反之, 超时返回默认结果。
前面的章节介绍了等待/通知的经典范式,即加锁、条件循环和处理逻辑3个步骤,而这种 范式无法做到超时等待。而超时等待的加入,只需要对经典范式做出非常小的改动,改动内容 如下所示。
假设超时时间段是T, 那么可以推断出在当前时间NOW+T之后就会超时。 定义如下变量。
·等待持续时间: REMAINING=T。
·超时时间: FUTURE=now+ T。
这时仅需要wait(REMAINING)即可,在wait(REMAINING)返回之后会将执行:
REMAINING=FUTURE一now。如果REMAINING小于等于0, 表示已经超时,直接退出,否则将 继续执行wait(REMAINING)。
上述描述等待超时模式的伪代码如下。
文章图片
可以看出,等待超时模式就是在等待/通知范式基础上增加了超时控制,这使得该模式相 比原有范式更具有灵活性,因为即使方法执行时间过长,也不会“永久"阻塞调用者,而是会按 照调用者的要求”按时“返回。
一个简单的数据库连接池示例 我们使用等待超时模式来构造一个简单的数据库连接池,在示例中模拟从连接池中获 取、使用和释放连接的过程,而客户端获取连接的过程被设定为等待超时的模式,也就是在 1000毫秒内如果无法获取到可用连接,将会返回给客户端一个null。设定连接池的大小为10 个,然后通过调节客户端的线程数来模拟无法获取连接的场景。
首先看一下连接池的定义。它通过构造函数初始化连接的最大上限,通过一个双向队列 来维护连接,调用方需要先调用fetchConnecti on(long)方法来指定在多少毫秒内超时获取连接, 当连接使用完成后,需要调用releaseConnecti on(Connection)方法将连接放回线程池,示例如代 码清单所示。
文章图片
文章图片
由于j ava.sql. Connection是一个接口,最终的实现是由数据库驱动提供方来实现的,考虑到 只是个示例,我们通过动态代理构造了一个Connection, 该Connection的代理实现仅仅是在 commit()方法调用时休眠100毫秒,示例如
文章图片
下面通过一个示例来测试简易数据库连接池的工作情况,模拟客户端CannectianRunner获 取、使用、最后释放连接的过程,当它使用时连接将会增加获取到连接的数量,反之,将会增 加未获取到连接的数量,示例如
文章图片
文章图片
【java虚拟机|java多线程 线程应用实例】上述示例中使用了CountDownLatch来确保Connecti onR.unnerThread能够同时开始执行,并 且在全部结束之后,才使main线程从等待状态中返回。当前设定的场景是10个线程同时运行 获取连接池(10个连接)中的连接,通过调节线程数量来观察未获取到连接的情况。线程数、总 获取次数、获取到的数量、未获取到的数量以及未获取到的比率,如
文章图片
从表中的数据统计可以看出,在资源一定的情况下(连接池中的10个连接),随着客户端 线程的逐步增加,客户端出现超时无法获取连接的比率不断升高。虽然客户端线程在这种超 时获取的模式下会出现连接无法获取的情况,但是它能够保证客户端线程不会一直挂在连接 获取的操作上,而是按时“返回,并告知客户端连接获取出现问题,是系统的一种自我保护机 制。数据库连接池的设计也可以复用到其他的资源获取的场景,针对昂贵资源(比如数据库连 接)的获取都应该加以超时限制。
线程池技术及其示例 对于服务端的程序,经常面对的是客户端传入的短小(执行时间短、工作内容较为单一) 任务,需要服务端快速处理并返回结果。如果服务端每次接受到一个任务,创建一个线程,然 后进行执行,这在原型阶段是个不错的选择,但是面对成千上万的任务递交进服务器时,如果 还是采用一个任务一个线程的方式,那么将会创建数以万记的线程,这不是一个好的选择。因 为这会使操作系统频繁的进行线程上下文切换,无故增加系统的负载,而线程的创建和消亡 都是需要耗费系统资源的,也无疑浪费了系统资源。
线程池技术能够很好地解决这个问题,它预先创建了若干数量的线程,并且不能由用户 直接对线程的创建进行控制,在这个前提下重复使用固定或较为固定数目的线程来完成任务 的执行。这样做的好处是,一方面,消除了频繁创建和消亡线程的系统资源开销,另一方面, 面对过量任务的提交能够平缓的劣化。
客户端可以通过execute(Job)方法将Job提交入线程池执行,而客户端自身不用等待Job的 执行完成。除了execute(Job)方法以外,线程池接口提供了增大/减少工作者线程以及关闭线程 池的方法。这里工作者线程代表着一个重复执行Job的线程,而每个由客户端提交的Job都将进入到一个工作队列中等待工作者线程的处理。
从线程池的实现可以看到,当客户端调用e:xecute(Job)方法时,会不断地向任务列表jobs中 添加Job, 而每个工作者线程会不断地从jobs上取出一个Job进行执行,当jobs为空时,工作者线 程进入等待状态。
添加一个Job后,对工作队列jobs调用了其notify()方法,而不是notify All()方法,因为能够 确定有工作者线程被唤醒,这时使用notify()方法将会比notify All()方法获得更小的开销(避免 将等待队列中的线程全部移动到阻塞队列中)。
可以看到线程池的本质就是使用了一个线程安全的工作队列连接工作者线程和客户端 线程,客户端线程将任务放入工作队列后便返回,而工作者线程则不断地从工作队列上取出 工作并执行。当工作队列为空时,所有的工作者线程均等待在工作队列上,当有客户端提交了 一个任务之后会通知任意一个工作者线程,随着大量的任务被提交,更多的工作者线程会被 唤醒。
文章图片
文章图片
文章图片
文章图片
一个基于线程池技术的简单Web服务器 目前的浏览器都支持多线程访问,比如说在请求一个HTML页面的时候,页面中包含的图 片资涌、样式资涌会被浏览器发起并发的获取,这样用户就不会遇到一直等到一个图片完全 下载完成才能继续查看文字内容的尴尬情况。
如果Web服务器是单线程的多线程的浏览器也没有用武之地,因为服务端还是一个请求 一个请求的顺序处理。因此,大部分Web服务器都是支持并发访问的。常用的Java Web服务器, 如Tomcat、Jetty, 在其处理请求的过程中都使用到了线程池技术。
下面通过使用前一节中的线程池来构造一个简单的Web服务器,这个Web服务器用来处理 HTTP请求,目前只能处理简单的文本和JPG图片内容。这个Web服务器使用main线程不断地接 受客户端Socket的连接,将连接以及请求提交给线程池处理,这样使得Web服务器能够同时处 理多个客户端请求,示例如代码
文章图片
文章图片
文章图片
SimplelittpServer在建立了与客户端的连接之后,并不会处理客户端的请求, 而是将其包装成HttpRequestliandler并交由线程池处理。在线程池中的Worker处理客户端请求 的同时,SimplelittpServer能够继续完成后续客户端连接的建立,不会阻塞后续客户端的请求。
文章图片
推荐阅读
- 面试|字节面试官告诉你如何面试研发岗
- Java|史上最全Java学习内容
- 面试|2021春招字节面经,4年老程序员经验分享,已拿到offer
- django|python 从docx文件中读取文字和图片,其中图片编码成base64格式(高中信息技术题库系统)
- JUC并发编程|JUC并发编程——线程池介绍和底层实现
- 游戏|一个niubility的Vue游戏,真厉害!
- 游戏|一个niubility的Vue游戏,真厉害!(收藏)
- HTML5|HTML5新增标签(一)
- 再也不敢精通Java了——get/set篇