Java5中的线程池实例讲解
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函数已经执行完毕,则取得返回值,如果还在执行,则等待其执行完毕。
服务器端的完整实现
服务器端的完整实现代码如下:
- package demo;
- import java.io.DataOutputStream;
- import java.io.IOException;
- import java.io.Serializable;
- import java.net.ServerSocket;
- import java.net.Socket;
- import java.util.concurrent.ArrayBlockingQueue;
- import java.util.concurrent.BlockingQueue;
- import java.util.concurrent.Callable;
- import java.util.concurrent.ExecutionException;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- import java.util.concurrent.Future;
- import java.util.concurrent.RejectedExecutionHandler;
- import java.util.concurrent.ThreadPoolExecutor;
- import java.util.concurrent.TimeUnit;
- import java.util.concurrent.locks.ReentrantLock;
- public class Server
- {
- private static int produceTaskSleepTime = 100;
- private static int consumeTaskSleepTime = 1200;
- private static int produceTaskMaxNumber = 100;
- private static final int CORE_POOL_SIZE = 2;
- private static final int MAX_POOL_SIZE = 100;
- private static final int KEEPALIVE_TIME = 3;
- private static final int QUEUE_CAPACITY = (CORE_POOL_SIZE + MAX_POOL_SIZE) / 2;
- private static final TimeUnit TIME_UNIT = TimeUnit.SECONDS;
- private static final String HOST = "127.0.0.1";
- private static final int PORT = 19527;
- private BlockingQueue workQueue = new ArrayBlockingQueue(QUEUE_CAPACITY);
- // private ThreadPoolExecutor serverThreadPool = null;
- private ExecutorService pool = null;
- private RejectedExecutionHandler rejectedExecutionHandler = new ThreadPoolExecutor.DiscardOldestPolicy();
- private ServerSocket serverListenSocket = null;
- private int times = 5;
- public void start()
- {
- // You can also init thread pool in this way.
- /*
- * serverThreadPool = new ThreadPoolExecutor(CORE_POOL_SIZE, MAX_POOL_SIZE, KEEPALIVE_TIME, TIME_UNIT, workQueue, rejectedExecutionHandler);
- */
- pool = Executors.newFixedThreadPool(10);
- try
- {
- serverListenSocket = new ServerSocket(PORT);
- serverListenSocket.setReuseAddress(true);
- System.out.println("I'm listening");
- while (times-- > 0)
- {
- Socket socket = serverListenSocket.accept();
- String welcomeString = "hello";
- // serverThreadPool.execute(new ServiceThread(socket, welcomeString));
- pool.execute(new ServiceThread(socket));
- }
- }
- catch (IOException e)
- {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- cleanup();
- }
- public void cleanup()
- {
- if (null != serverListenSocket)
- {
- try
- {
- serverListenSocket.close();
- }
- catch (IOException e)
- {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- // serverThreadPool.shutdown();
- pool.shutdown();
- //
调用 shutdown() 方法之后,主线程就马上结束了,而线程池会继续运行直到所有任务执行完才会停止。如果不调用 shutdown() 方法,
那么线程池会一直保持下去,以便随时添加新的任务。interrupt():只有阻塞(sleep,wait,join的线程调用他们的
interrupt()才起作用,正在运行的线程不起作用也不抛异常) - }
- public static void main(String args[])
- {
- Server server = new Server();
- server.start();
- }
- }
- class ServiceThread implements Runnable, Serializable
- {
- private static final long serialVersionUID = 0;
- private Socket connectedSocket = null;
- private String helloString = null;
- private static int count = 0;
- private static ReentrantLock lock = new ReentrantLock();
- ServiceThread(Socket socket)
- {
- connectedSocket = socket;
- }
- public void run()
- {
- increaseCount();
- int curCount = getCount();
- helloString = "hello, id = " + curCount + "\r\n";
- ExecutorService executor = Executors.newSingleThreadExecutor();
- Future<String> future = executor.submit(new TimeConsumingTask());
- DataOutputStream dos = null;
- try
- {
- dos = new DataOutputStream(connectedSocket.getOutputStream());
- dos.write(helloString.getBytes());
- try
- {
- dos.write("let's do soemthing other.\r\n".getBytes());
- String result = future.get();
- dos.write(result.getBytes());
- }
- catch (InterruptedException e)
- {
- e.printStackTrace();
- }
- catch (ExecutionException e)
- {
- e.printStackTrace();
- }
- }
- catch (IOException e)
- {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- finally
- {
- if (null != connectedSocket)
- {
- try
- {
- connectedSocket.close();
- }
- catch (IOException e)
- {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- if (null != dos)
- {
- try
- {
- dos.close();
- }
- catch (IOException e)
- {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- executor.shutdown();
- }
- }
- 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();
- }
- }
- }
- class TimeConsumingTask implements Callable<String>
- {
- 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";
- }
- }
Java5中的线程池实例讲解的更多相关文章
- 探究ElasticSearch中的线程池实现
探究ElasticSearch中的线程池实现 ElasticSearch里面各种操作都是基于线程池+回调实现的,所以这篇文章记录一下java.util.concurrent涉及线程池实现和Elasti ...
- java线程池实例
目的 了解线程池的知识后,写个线程池实例,熟悉多线程开发,建议看jdk线程池源码,跟大师比,才知道差距啊O(∩_∩)O 线程池类 package thread.pool2; impor ...
- C#中的线程池使用(一)
1 线程池的概念 许多应用程序使用多个线程,但这些线程经常在休眠状态中耗费大量的时间来等待事件发生.其他线程可能进入休眠状态,并且仅定期被唤醒以轮询更改或更新状态信息,然后再次进入休眠状态.为了简化 ...
- mina2中的线程池
一.Mina中的线程池模型 前面介绍了Mina总体的层次结构,那么在Mina里面是怎么使用Java NIO和进行线程调度的呢?这是提高IO处理性能的关键所在.Mina的线程调度原理主要如下图所示: A ...
- 【万字图文-原创】 | 学会Java中的线程池,这一篇也许就够了!
碎碎念 关于JDK源码相关的文章这已经是第四篇了,原创不易,粉丝从几十人到昨天的666人,真的很感谢之前帮我转发文章的一些朋友们. 从16年开始写技术文章,到现在博客园已经发表了222篇文章,大多数都 ...
- 《Java并发编程的艺术》 第9章 Java中的线程池
第9章 Java中的线程池 在开发过程中,合理地使用线程池能带来3个好处: 降低资源消耗.通过重复利用已创建的线程 降低线程创建和销毁造成的消耗. 提高响应速度.当任务到达时,任务可以不需要等到线程创 ...
- unix中的线程池技术详解
•线程池就是有一堆已经创建好了的线程,当有新的任务需要处理的时候,就从这个池子里面取一个空闲等待的线程来处理该任务,当处理完成了就再次把该线程放回池中,以供后面的任务使用,当池子里的线程全都处理忙碌状 ...
- 线程池原理讲解——ThreadPoolExecutor
[这是前几天的存货,留着没发表,今天又复习一遍,润化了部分内容,继续干] 说线程池前,先简单回顾一下线程的状态吧: 1.线程状态转换 线程的五种状态,及其转换关系: 2.线程创建方式 三种:两个接口一 ...
- android中的线程池学习笔记
阅读书籍: Android开发艺术探索 Android开发进阶从小工到专家 对线程池原理的简单理解: 创建多个线程并且进行管理,提交的任务会被线程池指派给其中的线程进行执行,通过线程池的统一调度和管理 ...
随机推荐
- Makefile第二讲:打印出内容和使用变量
摘要 `@echo "开始生成最终执行文件,请稍候..."`这一句便是将一条信息输出到终端,为何前边有个`@`符号呢?有了这个符号该命令本身就不会输出到终端(不理解,自己去掉或者加 ...
- List 三种遍历的方法
学习java语言list遍历的三种方法,顺便测试各种遍历方法的性能,测试方法为在ArrayList中插入1千万条记录,然后遍历ArrayList,发现了一个奇怪的现象,测试代码如下: package ...
- 【原】现有市场上H264 IPCamerad的功能
网络: 1.内置Web Server,通过IE实现远程监看.控制.设置等操作: 2.支持UPnP路由器,自动配置端口映射: 3.支持DDNS(动态域名解析).PPPoE拨号.DHCP网络协议: 4.支 ...
- Ubuntu---2
1.Ubuntu学习笔记之:安装中文语言包 http://askubuntu.com/questions/59356/how-do-i-get-chinese-input-to-work
- Bzoj 1616: [Usaco2008 Mar]Cow Travelling游荡的奶牛 动态规划
1616: [Usaco2008 Mar]Cow Travelling游荡的奶牛 Time Limit: 5 Sec Memory Limit: 64 MBSubmit: 1006 Solved: ...
- HW4.29
import java.util.Scanner; public class Solution { public static void main(String[] args) { Scanner i ...
- Genotype&&陨石的秘密
Genotype: Genotype 是一个有限的基因序列.它是由大写的英文字母A-Z组成,不同的字母表示不同种类的基因.一个基因可以分化成为一对新的基因.这种分化被一个定义的规则集合所控制.每个分化 ...
- Sruts2文件上传的ContentType的取值列表
".*"="application/octet-stream" ".001"="application/x-001" & ...
- 浙大PTA - - 堆中的路径
题目链接:https://pta.patest.cn/pta/test/1342/exam/4/question/21731 本题即考察最小堆的基本操作: #include "iostrea ...
- MySQL执行计划 EXPLAIN参数
MySQL执行计划参数详解 转http://www.jianshu.com/p/7134286b3a09 MySQL数据库中,在SELECT查询语句前边加上“EXPLAIN”或者“DESC”关键字,即 ...