1.10 多线程端点服务发布程序
Multithreading the Endpoint Publisher
以往的例子中,端点发布程序都是单线程的,也就是说在同一时刻只能并发处理一个客户端请求。在处理下一个请求之前,必须要等发布的服务完成当前正在处理的过程。如果当前正在处理的服务请求被挂起,在这个被挂起的请求处理完毕之前,其他后续的服务请求都无法进行。
在正式的产品环境中,Endpoint发布程序需要并发处理多个客户端请求,这样多个不可预期的客户端请求就可以在同一时间被处理。针对底层的计算机系统而言,比如对称多处理器(SMP,symmetric
multiprocessor)系统,每一个独立的CPU可以并发地处理不同客户端请求。而在一个单处理器计算机环境中,可以通过时间共享机制来完成同一时间内客户端请求的并发处理。Java语言支持多线程并发处理机制。因此,需要我们解决的就是如何使端点发布程序也支持多线程处理机制。JWS框架支持端点多线程模式,程序人员无须面对复杂而又难以控制的同步块(Synchronized
Block)、wait和notify方法调用等。
Endpoint对象有一个定义了标准get/set方法的Executor属性。Excutor用来执行可运行的(Runnable)任务;比如一个Java Thread实例。(Java中Runnable接口只定义了一个无返回值的公共接口方法:run()。)Executor对象对多线程处理来说是一个不错的选择,因为Executor针对被执行任务的提交和管理提供了高度封装。使端点发布程序支持多线程的第一步就是创建一个Executor类,代码如下:
- package ch01.ts;
- import java.util.concurrent.ThreadPoolExecutor;
- import java.util.concurrent.LinkedBlockingQueue;
- import java.util.concurrent.TimeUnit;
- import java.util.concurrent.locks.ReentrantLock;
- import java.util.concurrent.locks.Condition;
- public class MyThreadPool extends ThreadPoolExecutor {
- private static final int pool_size = 10;
- private boolean is_paused;
- private ReentrantLock pause_lock = new ReentrantLock();
- private Condition unpaused = pause_lock.newCondition();
- public MyThreadPool(){
- super(pool_size, // core pool size
- pool_size, // maximum pool size
- 0L, // keep-alive time for idle thread
- TimeUnit.SECONDS, // time unit for keep-alive setting
- new LinkedBlockingQueue<Runnable>(pool_size)); // work queue
- }
- // some overrides
- protected void beforeExecute(Thread t, Runnable r) {
- super.beforeExecute(t, r);
- pause_lock.lock();
- try {
- while (is_paused) unpaused.await();
- }
- catch (InterruptedException e) { t.interrupt(); }
- finally { pause_lock.unlock(); }
- }
- public void pause() {
- pause_lock.lock();
- try {
- is_paused = true;
- }
- finally { pause_lock.unlock(); }
- }
- public void resume() {
- pause_lock.lock();
- try {
- is_paused = false;
- unpaused.signalAll();
- }
- finally { pause_lock.unlock(); }
- }
- }
MyThreadPool创建了一个线程数为10的线程池,线程使用固定大小的队列来存储。当使用线程池时,在线程池中有空闲线程被释放出来之前,排队中的下一个任务必须等待。而这些管理的细节都由线程池来自动处理。MyThreadPool重写了ThreadPoolExecutor中的几个方法。
使用MyThreadPool对象,端点发布程序可以支持多线程处理。下面是在前发布程序的基础上修改后的代码,通过不同方法将发布任务分解:
- package ch01.ts;
- import javax.xml.ws.Endpoint;
- class TimePublisherMT { // MT for multithreaded
- private Endpoint endpoint;
- public static void main(String[ ] args) {
- TimePublisherMT self = new TimePublisherMT();
- self.create_endpoint();
- self.configure_endpoint();
- self.publish();
- }
- private void create_endpoint() {
- endpoint = Endpoint.create(new TimeServerImpl());
- }
- private void configure_endpoint() {
- endpoint.setExecutor(new MyThreadPool());
- }
- private void publish() {
- int port = 8888;
- String url = "http://localhost:" + port + "/ts";
- endpoint.publish(url);
- System.out.println("Publishing TimeServer on port " + port);
- }
- }
ThreadPoolWorker编写完后,剩余的工作就是设置本例中Endpoint实例的executor属性,而发布程序并不会涉及线程管理的任何细节。
多线程端点发布程序比较适合一些轻量级别的产品,但是在真正的应用场景中,上面这个发布程序并不是一个服务容器;服务容器可以在同一个端口发布多个Web服务。一些Web容器,比如Tomcat,提供了这些功能实现,比较适合于在发布多个Web服务时使用。后面例子中的Web服务将会介绍如何通过Tomcat发布。