Java5增加了新的类库并发集java.util.concurrent,该类库为并发程序提供了丰富的API多线程编程在Java 5中更加容易,灵活。本文通过一个网络服务器模型,来实践Java5的多线程编程,该模型中使用了Java5中的线程池,阻塞队列,可重入锁等,还实践了 Callable, Future等接口,并使用了Java 5的另外一个新特性泛型。

  简介

  本文将实现一个网络服务器模型,一旦有客户端连接到该服务器,则启动一个新线程为该连接服务,服务内容为往客户端输送一些字符信息。一个典型的网络服务器模型如下:

  1. 建立监听端口。

  2. 发现有新连接,接受连接,启动线程,执行服务线程。 3. 服务完毕,关闭线程。

  这个模型在大部分情况下运行良好,但是需要频繁的处理用户请求而每次请求需要的服务又是简短的时候,系统会将大量的时间花费在线程的创建销
毁。Java
5的线程池克服了这些缺点。通过对重用线程来执行多个任务,避免了频繁线程的创建与销毁开销,使得服务器的性能方面得到很大提高。因此,本文的网络服务器
模型将如下:

  1. 建立监听端口,创建线程池。

  2. 发现有新连接,使用线程池来执行服务任务。

  3. 服务完毕,释放线程到线程池。

  下面详细介绍如何使用Java 5的concurrent包提供的API来实现该服务器。

  初始化

  初始化包括创建线程池以及初始化监听端口。创建线程池可以通过调用java.util.concurrent.Executors类里的静态
方法newChahedThreadPool或是newFixedThreadPool来创建,也可以通过新建一个
java.util.concurrent.ThreadPoolExecutor实例来执行任务。这里我们采用newFixedThreadPool方
法来建立线程池。

ExecutorService pool = Executors.newFixedThreadPool(10);

  表示新建了一个线程池,线程池里面有10个线程为任务队列服务。

  使用ServerSocket对象来初始化监听端口。

private static final int PORT = 19527;

serverListenSocket = new ServerSocket(PORT);

serverListenSocket.setReuseAddress(true);

serverListenSocket.setReuseAddress(true);

  服务新连接

  当有新连接建立时,accept返回时,将服务任务提交给线程池执行。

while(true){

 Socket socket = serverListenSocket.accept();

 pool.execute(new ServiceThread(socket));

}

  这里使用线程池对象来执行线程,减少了每次线程创建和销毁的开销。任务执行完毕,线程释放到线程池。

  服务任务

  服务线程ServiceThread维护一个count来记录服务线程被调用的次数。每当服务任务被调用一次时,count的值自增1,因此
ServiceThread提供一个increaseCount和getCount的方法,分别将count值自增1和取得该count值。由于可能多个
线程存在竞争,同时访问count,因此需要加锁机制,在Java 5之前,我们只能使用synchronized来锁定。Java
5中引入了性能更加粒度更细的重入锁ReentrantLock。我们使用ReentrantLock保证代码线程安全。下面是具体代码:

private static ReentrantLock lock = new ReentrantLock ();

private static int count = 0;

private int getCount(){

 int ret = 0;

 try{

  lock.lock();

  ret = count;

 }finally{

  lock.unlock();

 }

 return ret;

}

private void increaseCount(){

 try{

  lock.lock();

  ++count;

 }finally{

  lock.unlock();

 }

}

  服务线程在开始给客户端打印一个欢迎信息,

increaseCount();

int curCount = getCount();

helloString = "hello, id = " + curCount+"\r\n";

dos = new DataOutputStream(connectedSocket.getOutputStream());

dos.write(helloString.getBytes());

  然后使用ExecutorService的submit方法提交一个Callable的任务,返回一个Future接口的引用。这种做法对费
时的任务非常有效,submit任务之后可以继续执行下面的代码,然后在适当的位置可以使用Future的get方法来获取结果,如果这时候该方法已经执
行完毕,则无需等待即可获得结果,如果还在执行,则等待到运行完毕。

ExecutorService executor = Executors.newSingleThreadExecutor();

Future future = executor.submit(new TimeConsumingTask());

dos.write("let's do soemthing other".getBytes());

String result = future.get();

dos.write(result.getBytes());

  其中TimeConsumingTask实现了Callable接口

class TimeConsumingTask implements Callable {

 public String call() throws Exception {

  System.out.println("It's a time-consuming task, you'd better retrieve your result in the furture");

  return "ok, here's the result: It takes me lots of time to produce this result";

 }

}

  这里使用了Java
5的另外一个新特性泛型,声明TimeConsumingTask的时候使用了String做为类型参数。必须实现Callable接口的call函数,
其作用类似与Runnable中的run函数,在call函数里写入要执行的代码,其返回值类型等同于在类声明中传入的类型值。在这段程序中,我们提交了
一个Callable的任务,然后程序不会堵塞,而是继续执行dos.write("let's do soemthing
other".getBytes());当程序执行到String result =
future.get()时如果call函数已经执行完毕,则取得返回值,如果还在执行,则等待其执行完毕。

服务器端的完整实现

  服务器端的完整实现代码如下:

    1. package demo;
    2. import java.io.DataOutputStream;
    3. import java.io.IOException;
    4. import java.io.Serializable;
    5. import java.net.ServerSocket;
    6. import java.net.Socket;
    7. import java.util.concurrent.ArrayBlockingQueue;
    8. import java.util.concurrent.BlockingQueue;
    9. import java.util.concurrent.Callable;
    10. import java.util.concurrent.ExecutionException;
    11. import java.util.concurrent.ExecutorService;
    12. import java.util.concurrent.Executors;
    13. import java.util.concurrent.Future;
    14. import java.util.concurrent.RejectedExecutionHandler;
    15. import java.util.concurrent.ThreadPoolExecutor;
    16. import java.util.concurrent.TimeUnit;
    17. import java.util.concurrent.locks.ReentrantLock;
    18. public class Server
    19. {
    20. private static int produceTaskSleepTime = 100;
    21. private static int consumeTaskSleepTime = 1200;
    22. private static int produceTaskMaxNumber = 100;
    23. private static final int CORE_POOL_SIZE = 2;
    24. private static final int MAX_POOL_SIZE = 100;
    25. private static final int KEEPALIVE_TIME = 3;
    26. private static final int QUEUE_CAPACITY = (CORE_POOL_SIZE + MAX_POOL_SIZE) / 2;
    27. private static final TimeUnit TIME_UNIT = TimeUnit.SECONDS;
    28. private static final String HOST = "127.0.0.1";
    29. private static final int PORT = 19527;
    30. private BlockingQueue workQueue = new ArrayBlockingQueue(QUEUE_CAPACITY);
    31. // private ThreadPoolExecutor serverThreadPool = null;
    32. private ExecutorService pool = null;
    33. private RejectedExecutionHandler rejectedExecutionHandler = new ThreadPoolExecutor.DiscardOldestPolicy();
    34. private ServerSocket serverListenSocket = null;
    35. private int times = 5;
    36. public void start()
    37. {
    38. // You can also init thread pool in this way.
    39. /*
    40. * serverThreadPool = new ThreadPoolExecutor(CORE_POOL_SIZE, MAX_POOL_SIZE, KEEPALIVE_TIME, TIME_UNIT, workQueue, rejectedExecutionHandler);
    41. */
    42. pool = Executors.newFixedThreadPool(10);
    43. try
    44. {
    45. serverListenSocket = new ServerSocket(PORT);
    46. serverListenSocket.setReuseAddress(true);
    47. System.out.println("I'm listening");
    48. while (times-- > 0)
    49. {
    50. Socket socket = serverListenSocket.accept();
    51. String welcomeString = "hello";
    52. // serverThreadPool.execute(new ServiceThread(socket, welcomeString));
    53. pool.execute(new ServiceThread(socket));
    54. }
    55. }
    56. catch (IOException e)
    57. {
    58. // TODO Auto-generated catch block
    59. e.printStackTrace();
    60. }
    61. cleanup();
    62. }
    63. public void cleanup()
    64. {
    65. if (null != serverListenSocket)
    66. {
    67. try
    68. {
    69. serverListenSocket.close();
    70. }
    71. catch (IOException e)
    72. {
    73. // TODO Auto-generated catch block
    74. e.printStackTrace();
    75. }
    76. }
    77. // serverThreadPool.shutdown();
    78. pool.shutdown();
    79. //
      调用 shutdown() 方法之后,主线程就马上结束了,而线程池会继续运行直到所有任务执行完才会停止。如果不调用 shutdown() 方法,
      那么线程池会一直保持下去,以便随时添加新的任务。interrupt():只有阻塞(sleep,wait,join的线程调用他们的
      interrupt()才起作用,正在运行的线程不起作用也不抛异常)
    80. }
    81. public static void main(String args[])
    82. {
    83. Server server = new Server();
    84. server.start();
    85. }
    86. }
    87. class ServiceThread implements Runnable, Serializable
    88. {
    89. private static final long serialVersionUID = 0;
    90. private Socket connectedSocket = null;
    91. private String helloString = null;
    92. private static int count = 0;
    93. private static ReentrantLock lock = new ReentrantLock();
    94. ServiceThread(Socket socket)
    95. {
    96. connectedSocket = socket;
    97. }
    98. public void run()
    99. {
    100. increaseCount();
    101. int curCount = getCount();
    102. helloString = "hello, id = " + curCount + "\r\n";
    103. ExecutorService executor = Executors.newSingleThreadExecutor();
    104. Future<String> future = executor.submit(new TimeConsumingTask());
    105. DataOutputStream dos = null;
    106. try
    107. {
    108. dos = new DataOutputStream(connectedSocket.getOutputStream());
    109. dos.write(helloString.getBytes());
    110. try
    111. {
    112. dos.write("let's do soemthing other.\r\n".getBytes());
    113. String result = future.get();
    114. dos.write(result.getBytes());
    115. }
    116. catch (InterruptedException e)
    117. {
    118. e.printStackTrace();
    119. }
    120. catch (ExecutionException e)
    121. {
    122. e.printStackTrace();
    123. }
    124. }
    125. catch (IOException e)
    126. {
    127. // TODO Auto-generated catch block
    128. e.printStackTrace();
    129. }
    130. finally
    131. {
    132. if (null != connectedSocket)
    133. {
    134. try
    135. {
    136. connectedSocket.close();
    137. }
    138. catch (IOException e)
    139. {
    140. // TODO Auto-generated catch block
    141. e.printStackTrace();
    142. }
    143. }
    144. if (null != dos)
    145. {
    146. try
    147. {
    148. dos.close();
    149. }
    150. catch (IOException e)
    151. {
    152. // TODO Auto-generated catch block
    153. e.printStackTrace();
    154. }
    155. }
    156. executor.shutdown();
    157. }
    158. }
    159. private int getCount()
    160. {
    161. int ret = 0;
    162. try
    163. {
    164. lock.lock();
    165. ret = count;
    166. }
    167. finally
    168. {
    169. lock.unlock();
    170. }
    171. return ret;
    172. }
    173. private void increaseCount()
    174. {
    175. try
    176. {
    177. lock.lock();
    178. ++count;
    179. }
    180. finally
    181. {
    182. lock.unlock();
    183. }
    184. }
    185. }
    186. class TimeConsumingTask implements Callable<String>
    187. {
    188. public String call() throws Exception
    189. {
    190. System.out.println("It's a time-consuming task, you'd better retrieve your result in the furture");
    191. return "ok, here's the result: It takes me lots of time to produce this result";
    192. }
    193. }

Java5中的线程池实例讲解的更多相关文章

  1. 探究ElasticSearch中的线程池实现

    探究ElasticSearch中的线程池实现 ElasticSearch里面各种操作都是基于线程池+回调实现的,所以这篇文章记录一下java.util.concurrent涉及线程池实现和Elasti ...

  2. java线程池实例

    目的         了解线程池的知识后,写个线程池实例,熟悉多线程开发,建议看jdk线程池源码,跟大师比,才知道差距啊O(∩_∩)O 线程池类 package thread.pool2; impor ...

  3. C#中的线程池使用(一)

    1  线程池的概念 许多应用程序使用多个线程,但这些线程经常在休眠状态中耗费大量的时间来等待事件发生.其他线程可能进入休眠状态,并且仅定期被唤醒以轮询更改或更新状态信息,然后再次进入休眠状态.为了简化 ...

  4. mina2中的线程池

    一.Mina中的线程池模型 前面介绍了Mina总体的层次结构,那么在Mina里面是怎么使用Java NIO和进行线程调度的呢?这是提高IO处理性能的关键所在.Mina的线程调度原理主要如下图所示: A ...

  5. 【万字图文-原创】 | 学会Java中的线程池,这一篇也许就够了!

    碎碎念 关于JDK源码相关的文章这已经是第四篇了,原创不易,粉丝从几十人到昨天的666人,真的很感谢之前帮我转发文章的一些朋友们. 从16年开始写技术文章,到现在博客园已经发表了222篇文章,大多数都 ...

  6. 《Java并发编程的艺术》 第9章 Java中的线程池

    第9章 Java中的线程池 在开发过程中,合理地使用线程池能带来3个好处: 降低资源消耗.通过重复利用已创建的线程 降低线程创建和销毁造成的消耗. 提高响应速度.当任务到达时,任务可以不需要等到线程创 ...

  7. unix中的线程池技术详解

    •线程池就是有一堆已经创建好了的线程,当有新的任务需要处理的时候,就从这个池子里面取一个空闲等待的线程来处理该任务,当处理完成了就再次把该线程放回池中,以供后面的任务使用,当池子里的线程全都处理忙碌状 ...

  8. 线程池原理讲解——ThreadPoolExecutor

    [这是前几天的存货,留着没发表,今天又复习一遍,润化了部分内容,继续干] 说线程池前,先简单回顾一下线程的状态吧: 1.线程状态转换 线程的五种状态,及其转换关系: 2.线程创建方式 三种:两个接口一 ...

  9. android中的线程池学习笔记

    阅读书籍: Android开发艺术探索 Android开发进阶从小工到专家 对线程池原理的简单理解: 创建多个线程并且进行管理,提交的任务会被线程池指派给其中的线程进行执行,通过线程池的统一调度和管理 ...

随机推荐

  1. HDJ -- 1022

    #include<iostream> #include<cstdio> #include<string> #define MAXN 10 using namespa ...

  2. devi into python 笔记(二)元组 变量声明 和列表解析

    元组tuple: 类似list,只是tuple是不可变的list.类似java的String都是不可改变的.注意:tuple没有方法(有待考证),不可以像list那样那个list.pop 或者list ...

  3. yml文件数据的简洁表达方法(Hashes to OpenStruct)

    通过ruby编写测试脚本的时候,我还是喜欢采用yml来管理测试数据,就像以前的文章(Selenium WebDriver + Grid2 + RSpec之旅(五))提到的一样,但是在引用yml中的数据 ...

  4. [并发编程]使用线程安全队列和条件变量的notify来安排分步骤任务

    // 方法1:直接构建N个THread来run foreach (i, size) { thread trd(&Instance::doWork, &inst); lstTrd.pus ...

  5. java基础(十四)集合(一)

    这里有我之前上课总结的一些知识点以及代码大部分是老师讲的笔记 个人认为是非常好的,,也是比较经典的内容,真诚的希望这些对于那些想学习的人有所帮助! 由于代码是分模块的上传非常的不便.也比较多,讲的也是 ...

  6. JAVA vo pojo javabean dto的区别

    JavaBean 是一种JAVA语言写成的可重用组件.为写成JavaBean,类必须是具体的和公共的,并且具有无参数的构造器.JavaBean 通过提供符合一致性设计模式的公共方法将内部域暴露成员属性 ...

  7. 每隔一段时间执行一次函数。window.setTimeout

    timer2 = window.setTimeout("showTaxi()", 30000);//30秒从后台获取一次数据,显示在地图上. 原来试过  setInterval . ...

  8. 主流智能手机屏幕材质介绍 及 LCD闪屏现象分析

    TN            TN(Twisted Nematic) 即扭曲向列型面板,属于有源矩阵液晶显示器中的一种.由于TFT是主动式矩阵LCD可让液晶的排列方式具有记忆性,不会在电流消失后马上恢复 ...

  9. ActiveMQ入门实例(转)

    1.下载ActiveMQ 去官方网站下载:http://activemq.apache.org/ 2.运行ActiveMQ 解压缩apache-activemq-5.5.1-bin.zip,然后双击a ...

  10. 部署war包到tomcat服务器

    1.首先通过eclipse的maven install生成项目的war包 2.然后把war包解压到tomcat目录下的webapps目录下 3.然后运行tomcat下的bin目录下的startup.b ...