JAVA后台程序设计及UTIL.CONCURRENT包的运用

2023-11-11 04:58

本文主要是介绍JAVA后台程序设计及UTIL.CONCURRENT包的运用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

JAVA后台程序设计及UTIL.CONCURRENT包的应用
JAVA后台程序设计及UTIL.CONCURRENT包的应用

何 恐

摘要 : 在很多软件项目中,JAVA语言常常被用来开发后台服务程序。线程池技术是提高这类程序性能的一个重要手段。在实践中,该技术已经被广泛的使用。本文首先 对设计后台服务程序通常需要考虑的问题进行了基本的论述,随后介绍了JAVA线程池的原理、使用和其他一些相关问题,最后对功能强大的JAVA开放源码线 程池包util.concurrent 在实际编程中的应用进行了详细介绍。
关键字: JAVA;线程池;后台服务程序;util.concurrent



1 引言
在软件项目开发中,许多后台服务程序的处理动作流程都具有一个相同点,就是:接受客户端发来的请求,对请求进行一些相关的处理,最后将处理结果返回给客户 端。这些请求的来源和方式可能会各不相同,但是它们常常都有一个共同点:数量巨大,处理时间短。这类服务器在实际应用中具有较大的普遍性,如web服务 器,短信服务器,DNS服务器等等。因此,研究如何提高此类后台程序的性能,如何保证服务器的稳定性以及安全性都具有重要的实用价值。

2 后台服务程序设计
2.1 关于设计原型
构建服务器应用程序的一个简单的模型是:启动一个无限循环,循环里放一个监听线程监听某个地址端口。每当一个请求到达就创建一个新线程,然后新线程为请求服务,监听线程返回继续监听。
简单举例如下:
import java.net.*;
public class MyServer extends Thread{
public void run(){
try{
ServerSocket server=null;
Socket clientconnection=null;
server = new ServerSocket(8008);//监听某地址端口对
while(true){进入无限循环
clientconnection =server.accept();//收取请求
new ServeRequest(clientconnection).start();//启动一个新服务线程进行服务
……
}
}catch(Exception e){
System.err.println("Unable to start serve listen:"+e.getMessage());
e.printStackTrace();
}
}
}
实际上,这只是个简单的原型,如果试图部署以这种方式运行的服务器应用程序,那么这种方法的严重不足就很明显。
首先,为每个请求创建一个新线程的开销很大,为每个请求创建新线程的服务器在创建和销毁线程上花费的时间和消耗的系统资源, 往往有时候要比花在处理实际的用户请求的时间和资源更多。在Java中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收。所以提 高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数。这样综合看来,系统的性能瓶颈就在于线程的创建开销。
其次,除了创建和销毁线程的开销之外,活动的线程也消耗系统资源。在一个 JVM 里创建太多的线程可能会导致系统由于过度消耗内存而用完内存或“切换过度”。为了防止资源不足,服务器应用程序需要一些办法来限制任何给定时刻运行的处理 线程数目,以防止服务器被“压死”的情况发生。所以在设计后台程序的时候,一般需要提前根据服务器的内存、CPU等硬件情况设定一个线程数量的上限值。
如果创建和销毁线程的时间相对于服务时间占用的比例较大,那末假设在一个较短的时间内有成千上万的请求到达,想象一下,服务器的时间和资源将会大量的花在 创建和销毁线程上,而真正用于处理请求的时间却相对较少,这种情况下,服务器性能瓶颈就在于创建和销毁线程的时间。按照这个模型写一个简单的程序测试一下 即可看出,由于篇幅关系,此处略。如果把(服务时间/创建和销毁线程的时间)作为衡量服务器性能的一个参数,那末这个比值越大,服务器的性能就越高。
应此,解决此类问题的实质就是尽量减少创建和销毁线程的时间,把服务器的资源尽可能多地用到处理请求上来,从而发挥多线程的优点(并发),避免多线程的缺点(创建和销毁的时空开销)。
线程池为线程生命周期开销问题和资源不足问题提供了解决方案。通过对多个任务重用线程,线程创建的开销被分摊到了多个任务上。其好处是,因为在请求到达时 线程已经存在,所以无意中也消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使应用程序响应更快。而且,通过适当地调整线程池中的线程数目,也 就是当请求的数目超过某个阈值时,就强制其它任何新到的请求一直等待,直到获得一个线程来处理为止,从而可以防止资源不足。

3    JAVA线程池原理
3.1 原理以及实现
在实践中,关于线程池的实现常常有不同的方法,但是它们的基本思路大都是相似的:服务器预先存放一定数目的“热”的线程,并发程序需要使用线程的时候,从 服务器取用一条已经创建好的线程(如果线程池为空则等待),使用该线程对请求服务,使用结束后,该线程并不删除,而是返回线程池中,以备复用,这样可以避 免对每一个请求都生成和删除线程的昂贵操作。
一个比较简单的线程池至少应包含线程池管理器、工作线程、任务队列、任务接口等部分。其中线程池管理器(ThreadPool Manager)的作用是创建、销毁并管理线程池,将工作线程放入线程池中;工作线程是一个可以循环执行任务的线程,在没有任务时进行等待;任务队列的作 用是提供一种缓冲机制,将没有处理的任务放在任务队列中;任务接口是每个任务必须实现的接口,主要用来规定任务的入口、任务执行完后的收尾工作、任务的执 行状态等,工作线程通过该接口调度任务的执行。下面的代码实现了创建一个线程池:
public class ThreadPool

private Stack threadpool = new Stack();
private int poolSize;
private int currSize=0;
public void setSize(int n)

poolSize = n;
}
public void run()
{
for(int i=0;i

(发帖时间:2003-11-30 11:55:56)  
--- 岑心   J

回复(1): 

4.2    框架与结构
下面让我们来看看util.concurrent的框架结构。关于这个工具包概述的e文原版链接地址是http: //gee.cs.oswego.edu/dl/cpjslides/util.pdf。该工具包主要包括三大部分:同步、通道和线程池执行器。第一部分 主要是用来定制锁,资源管理,其他的同步用途;通道则主要是为缓冲和队列服务的;线程池执行器则提供了一组完善的复杂的线程池实现。
--主要的结构如下图所示

4.2.1 Sync
acquire/release协议的主要接口
- 用来定制锁,资源管理,其他的同步用途
- 高层抽象接口
- 没有区分不同的加锁用法

实现
-Mutex, ReentrantLock, Latch, CountDown,Semaphore, WaiterPreferenceSemaphore, FIFOSemaphore, PrioritySemaphore
还有,有几个简单的实现,例如ObservableSync, LayeredSync

举例:如果我们要在程序中获得一独占锁,可以用如下简单方式:
try {
lock.acquire();
try {
action();
}
finally {
lock.release();
}
}catch(Exception e){
}

程序中,使用lock对象的acquire()方法获得一独占锁,然后执行您的操作,锁用完后,使用release()方法释放之即可。呵呵,简单吧,想 想看,如果您亲自撰写独占锁,大概会考虑到哪些问题?如果关键的锁得不到怎末办?用起来是不是会复杂很多?而现在,以往的很多细节和特殊异常情况在这里都 无需多考虑,您尽可以把精力花在解决您的应用问题上去。

4.2.2 通道(Channel)
为缓冲,队列等服务的主接口

具体实现
LinkedQueue, BoundedLinkedQueue,BoundedBuffer, BoundedPriorityQueue, SynchronousChannel, Slot

通道例子
class Service { // ...
final Channel msgQ = new LinkedQueue();
public void serve() throws InterruptedException {
String status = doService();
msgQ.put(status);
}
public Service() { // start background thread
Runnable logger = new Runnable() {
public void run() {
try {
for(;;)
System.out.println(msqQ.take());
}
catch(InterruptedException ie) {} }
};
new Thread(logger).start();
}
}
在后台服务器中,缓冲和队列都是最常用到的。试想,如果对所有远端的请求不排个队列,让它们一拥而上的去争夺cpu、内存、资源,那服务器瞬间不当掉才怪。而在这里,成熟的队列和缓冲实现已经提供,您只需要对其进行正确初始化并使用即可,大大缩短了开发时间。

4.2.3执行器(Executor)
Executor是这里最重要、也是我们往往最终写程序要用到的,下面重点对其进行介绍。
类似线程的类的主接口
- 线程池
- 轻量级运行框架
- 可以定制调度算法

只需要支持execute(Runnable r)
- 同Thread.start类似

实现
- PooledExecutor, ThreadedExecutor, QueuedExecutor, FJTaskRunnerGroup

PooledExecutor(线程池执行器)是个最常用到的类,以它为例:
可修改得属性如下:
- 任务队列的类型
- 最大线程数
- 最小线程数
- 预热(预分配)和立即(分配)线程
- 保持活跃直到工作线程结束
-- 以后如果需要可能被一个新的代替
- 饱和(Saturation)协议
-- 阻塞,丢弃,生产者运行,等等

可不要小看上面这数条属性,对这些属性的设置完全可以等同于您自己撰写的线程池的成百上千行代码。下面以笔者撰写过得一个GIS服务器为例:
该GIS服务器是一个典型的“请求-服务”类型的服务器,遵循后端程序设计的一般框架。首先对所有的请求按照先来先服务排入一个请求队列,如果瞬间到达的 请求超过了请求队列的容量,则将溢出的请求转移至一个临时队列。如果临时队列也排满了,则对以后达到的请求给予一个“服务器忙”的提示后将其简单抛弃。这 个就够忙活一阵的了。
然后,结合链表结构实现一个线程池,给池一个初始容量。如果该池满,以x2的策略将池的容量动态增加一倍,依此类推,直到总线程数服务达到系统能力上限, 之后线程池容量不在增加,所有请求将等待一个空余的返回线程。每从池中得到一个线程,该线程就开始最请求进行GIS信息的服务,如取坐标、取地图,等等。 服务完成后,该线程返回线程池继续为请求队列离地后续请求服务,周而复始。当时用矢量链表来暂存请求,用wait()、 notify() 和 synchronized等原语结合矢量链表实现线程池,总共约600行程序,而且在运行时间较长的情况下服务器不稳定,线程池被取用的线程有异常消失的 情况发生。而使用util.concurrent相关类之后,仅用了几十行程序就完成了相同的工作而且服务器运行稳定,线程池没有丢失线程的情况发生。由 此可见util.concurrent包极大的提高了开发效率,为项目节省了大量的时间。
使用PooledExecutor例子
import java.net.*;
/**
*

Title:


*

Description: 负责初始化线程池以及启动服务器


*

Copyright: Copyright (c) 2003


*

Company:


* @author not attributable
* @version 1.0
*/
public class MainServer {
//初始化常量
public static final int MAX_CLIENT=100; //系统最大同时服务客户数
//初始化线程池
public static final PooledExecutor pool =
new PooledExecutor(new BoundedBuffer(10), MAX_CLIENT); //chanel容量为10,
//在这里为线程池初始化了一个
//长度为10的任务缓冲队列。

public MainServer() {
//设置线程池运行参数
pool.setMinimumPoolSize(5); //设置线程池初始容量为5个线程
pool.discardOldestWhenBlocked();//对于超出队列的请求,使用了抛弃策略。
pool.createThreads(2); //在线程池启动的时候,初始化了具有一定生命周期的2个“热”线程
}

public static void main(String[] args) {
MainServer MainServer1 = new MainServer();
new HTTPListener().start();//启动服务器监听和处理线程
new manageServer().start();//启动管理线程
}
}

类HTTPListener
import java.net.*;
/**
*

Title:


*

Description: 负责监听端口以及将任务交给线程池处理


*

Copyright: Copyright (c) 2003


*

Company:


* @author not attributable
* @version 1.0
*/

public class HTTPListener extends Thread{
public HTTPListener() {
}
public void run(){
try{
ServerSocket server=null;
Socket clientconnection=null;
server = new ServerSocket(8008);//服务套接字监听某地址端口对
while(true){//无限循环
clientconnection =server.accept();
System.out.println("Client connected in!");
//使用线程池启动服务
MainServer.pool.execute(new HTTPRequest(clientconnection));//如果收到一个请求,则从线程池中取一个线程进行服务,任务完成后,该线程自动返还线程池
}
}catch(Exception e){
System.err.println("Unable to start serve listen:"+e.getMessage());
e.printStackTrace();
}
}
}

关于util.concurrent工具包就有选择的介绍到这,更详细的信息可以阅读这些java源代码的API文档。Doug Lea是个很具有“open”精神的作者,他将util.concurrent工具包的java源代码全部公布出来,有兴趣的读者可以下载这些源代码并细 细品味。 

5    结束语
以上内容介绍了线程池基本原理以及设计后台服务程序应考虑到的问题,并结合实例详细介绍了重要的多线程开发工具包util.concurrent的构架和使用。结合使用已有完善的开发包,后端服务程序的开发周期将大大缩短,同时程序性能也有了保障。



参考文献
[1] Chad Darby,etc. 《Beginning Java Networking》. 电子工业出版社. 2002年3月. 
[2] util.concurrent 说明文件 http://gee.cs.oswego.edu/dl/cpjslides/util.pdf 
[3] 幸勇.线程池的介绍及简单实现.http://www-900.ibm.com/developerWorks/cn/java/l-threadPool/index.shtml
2002年8月
[4]BrianGoetz. 我的线程到哪里去了http://www-900.cn.ibm.com/developerworks/cn/java/j-jtp0924/index.shtml

2002 年 12 月


转载地址:http://www.myexception.cn/software-architecture-design/865868.html

这篇关于JAVA后台程序设计及UTIL.CONCURRENT包的运用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/387685

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟 开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚 第一站:海量资源,应有尽有 走进“智听