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. Apache Log4j使用实例

    Apache Log4j使用实例  原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.  Blog:  1.Logger类 通过Logger类的静 ...

  2. ORACLE解决登陆em状态暂挂方法

    1.找到oracle安装目录中db_1/计算机名_orcl/sysman/config/emd.properties , 用记事本打开emd.properties,在emd.properties文件 ...

  3. 数学:lucas定理的总结

    今天考试的题目中有大组合数取模,不会唉,丢了45分,我真是个弱鸡,现在还不会lucas. 所以今天看了一下,定理差不多是: (1)Lucas定理:p为素数,则有: 即:lucas(n,m,p)=c(n ...

  4. Delphi的BPL介绍和使用 转

    了解BPL和DLL的关系将有助于我们更好地理解DELPHI在构件制作.运用和动态.静态编译的工作方式.对初学DELPHI但仍对DELPHI开发不甚清晰的朋友有一定帮助.BPL vs. DLL(原文ht ...

  5. TeamViewer 远程时出现:现在无法捕捉画面。这可能是由于恰的用户切换或远程桌面会话断开、最小化

    关掉远程访问主机的TeamViewer ,再重新打开软件

  6. MySQL的YEARWEEK函数(转)

    MySQL的YEARWEEK函数以及查询本周数据 2013-03-10 16:45:10     我来说两句      作者:kamuikyo 收藏    我要投稿 MySQL的YEARWEEK函数以 ...

  7. Interleaving String——Leetcode

    Given s1, s2, s3, find whether s3 is formed by the interleaving of s1 and s2. For example,Given:s1 = ...

  8. 向老项目JSP集成JSTL遇到的问题

    today,I Failed to load or instantiate TagLibraryValidator class - JstlCoreTLV <%@ page contentTyp ...

  9. Python中的引用的使用注意

    关于Python中的引用的一些使用注意 在python中,在创建一个对象并给它赋予一个变量时,这个赋予的变量仅仅是一个引用它所代表的对象.也就是说新创建的对象只是指向计算机中储存那个对象的内存. 比如 ...

  10. 泛型类、Map集合

    ————泛型: JDK1.5之后出现的新特性:用于解决安全问题,是一个类型安全机制. 好处: 1.将运行时期出现的问题ClassCastException ,转移到了编译时期,方便于程序员解决问题,让 ...