等待超时模式
开发人员经常会遇到这样的方法调用场景:调用一个方法时等待一段时间(一般来说是给定一个时间段),如果该方法能够在给定的时间段之内得到结果,那么将结果立刻返回,反之,超时返回默认结果。
等待/通知的经典范式,即加锁、条件循环和处理逻辑3个步骤,而这种范式无法做到超时等待。超时等待的加入,只需要对经典范式做出非常小的改动。
等待/通知的经典范式,即加锁、条件循环和处理逻辑3个步骤,而这种范式无法做到超时等待。超时等待的加入,只需要对经典范式做出非常小的改动。
一个简单的数据库连接池示例
//java.sql.Connection是一个接口,最终的实现是由数据库驱动提供方来实现。
//我们通过动态代理构造一个Connection,仅仅用于示范。
public class ConnectionDriver {
//动态代理的处理器类
static class ConnectionHandler implements InvocationHandler{
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Thread.sleep(100);
return null;
}
}
//创建一个Connection的代理,在commit时休眠100毫秒
public static final Connection createConnection(){
return (Connection)Proxy.newProxyInstance(ConnectionDriver.class.getClassLoader(),
new Class<?>[]{Connection.class}, new ConnectionHandler());
}
}
//连接池
public class ConnectionPool {
//通过一个双向队列来维护连接
private LinkedList<Connection> pool=new LinkedList<Connection>();
//构造函数,设定初始化连接池大小,并进行填充
public ConnectionPool(int initialSize){
for(int i=0;i<initialSize;i++){
pool.addLast(ConnectionDriver.createConnection());
}
}
//获取Connection,在mills内无法获取将返回null
public Connection fetchConnection(long mills) throws InterruptedException{
synchronized(pool){
if(mills<=0){
//无限等待模式
while(pool.isEmpty()){
pool.wait();
}
return pool.removeFirst();
}else{
//超时等待模式
long future=System.currentTimeMillis()+mills;
long remaining=mills;
while(pool.isEmpty()&&remaining>0){
pool.wait(remaining);
remaining=future-System.currentTimeMillis();
}
//条件满足(要么是超时,要么线程池容量非空。),处理任务
Connection result=null;
if(!pool.isEmpty()){
result=pool.removeFirst();
}
return result;
}
}
}
//释放Connection
public void releaseConnection(Connection connection){
synchronized(pool){
pool.addLast(connection);
pool.notifyAll();
}
}
}
public class ConnectionPoolTest {
static ConnectionPool pool=new ConnectionPool(10);
//保证所有的ConnectionRunner同时开始
static CountDownLatch start=new CountDownLatch(1);
//保证main线程等到所有ConnectionRunner结束后才能继续执行
static CountDownLatch end;
//一个不断尝试从连接池获取连接的任务
static class ConnectionRunner implements Runnable{
int count;//每条线程尝试获取次数
AtomicInteger got; //所有线程获取到的总数
AtomicInteger notgot;//所有线程没获取 到的总数
public ConnectionRunner(int count,AtomicInteger got,AtomicInteger notgot){
this.count=count;
this.got=got;
this.notgot=notgot;
}
@Override
public void run() {
try {
start.await();//等待start.countDown()的执行
} catch (InterruptedException e) {
e.printStackTrace();
}
while(count-->0){
try{
//从线程中获取连接,如果1000ms内无法获取到,将会返回null
//分别统计所有线程的:连接获取数got和连接失败数notgot
Connection connection=pool.fetchConnection(1000);
if(connection==null){
notgot.incrementAndGet();
}else{
try{
connection.createStatement();
connection.commit();
} finally{
pool.releaseConnection(connection);
got.incrementAndGet();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
end.countDown();
}
}
public static void main(String[] arg) throws InterruptedException{
//线程数量
int threadCount=30;
end=new CountDownLatch(threadCount);
int count=20;
AtomicInteger got=new AtomicInteger();
AtomicInteger notgot=new AtomicInteger();
for(int i=0;i<threadCount;i++){
Thread thread=new Thread(new ConnectionRunner(count,got,notgot),
"ConnectionRunnerThread"+i);
thread.start();
}
start.countDown();//发送启动命令
end.await();//等待所有ConnectionRunner线程结束
System.out.println("total invoke:"+(threadCount*count));
System.out.println("got connection:"+got.get());
System.out.println("not got connection:"+notgot.get());
}
}
从表中的数据统计可以看出,在资源一定的情况下(连接池中的10个连接),随着客户端线程的逐步增加,客户端出现超时无法获取连接的比率不断升高。虽然客户端线程在这种超时获取的模式下会出现连接无法获取的情况,但是它能够保证客户端线程不会一直挂在连接获取的操作上,而是“按时”返回,并告知客户端连接获取出现问题,是系统的一种自我保护机制。数据库连接池的设计也可以复用到其他的资源获取的场景,针对昂贵资源(比如数据库连接)的获取都应该加以超时限制。
线程池技术及其示例
对于服务端的程序,经常面对的是客户端传入的短小(执行时间短、工作内容较为单一)任务,需要服务端快速处理并返回结果。如果服务端每次接受到一个任务,创建一个线程,然后进行执行,这在原型阶段是个不错的选择,但是面对成千上万的任务递交进服务器时,如果还是采用一个任务一个线程的方式,那么将会创建数以万记的线程,这不是一个好的选择。因为这会使操作系统频繁的进行线程上下文切换,无故增加系统的负载,而线程的创建和消亡都是需要耗费系统资源的,也无疑浪费了系统资源。线程池技术能够很好地解决这个问题,它预先创建了若干数量的线程,并且不能由用户直接对线程的创建进行控制,在这个前提下重复使用固定或较为固定数目的线程来完成任务的执行。这样做的好处是,一方面,消除了频繁创建和消亡线程的系统资源开销,另一方面,面对过量任务的任务提交能够平缓的劣化。
public interface ThreadPool<Job extends Runnable> {
//执行一个Job,这个Job需要实现Runnable接口
void execute(Job job);
//关闭线程池
void shutDown();
//增加工作者线程
void addWorkers(int num);
//减少工作者线程
void removeWorker(int num);
//得到正在等待执行的任务数量
int getJobSize();
}
客户端可以通过execute(Job)方法将Job提交入线程池执行,而客户端自身不用等待Job的执行完成。除了execute(Job)方法以外,线程池接口提供了增大/减少工作者线程以及关闭线程池的方法。这里工作者线程代表着一个重复执行Job的线程,而每个由客户端提交的Job都将进入到一个工作队列中等待工作者线程的处理。
public class DefaultThreadPool<Job extends Runnable> implements ThreadPool<Job> {
//最大线程数
private static final int MaxWorkerNumber=10;
//默认线程数
private static final int DefaultWorkerNumber=5;
//最小线程数
private static final int MinWorkerNumber=1;
//这是一个任务队列,将会向里面插入任务
private final LinkedList<Job> jobs=new LinkedList<Job>();
//工作者列表
private final List<Worker> workers=
Collections.synchronizedList(new ArrayList<Worker>());
//工作者线程的数量
private int workerNum=DefaultWorkerNumber;
//线程编号生成
private AtomicLong threadNum=new AtomicLong();
public DefaultThreadPool(){
initializeWorker(DefaultWorkerNumber);
}
public DefaultThreadPool(int num){
workerNum=num>MaxWorkerNumber?MaxWorkerNumber:
num<MinWorkerNumber?MinWorkerNumber:num;
initializeWorker(workerNum);
}
//初始化工作者
private void initializeWorker(int num){
for(int i=0;i<num;i++){
Worker worker=new Worker();
workers.add(worker);
Thread thread=new Thread(worker,"ThreadPool-Worker-"+threadNum.incrementAndGet());
System.out.println("添加工作者线程:ThreadPool-Worker-"+threadNum.get());
thread.start();
}
}
@Override
public void execute(Job job) {
if(job!=null){
//添加一个任务,然后进行通知
synchronized(jobs){
jobs.addLast(job);
jobs.notifyAll();
System.out.println("提交任务:"+job.toString());
}
}
}
@Override
public void shutDown() {
for(Worker worker:workers){
worker.shutdown();
}
System.err.println(" 关闭线程池! ");
//注意这里需要通知等待队列中的工作者线程
synchronized(jobs){
jobs.notifyAll();
}
}
@Override
public void addWorkers(int num) {
//限定新增的Worder数量不能超过最大值
if(num+this.workerNum>MaxWorkerNumber){
num=MaxWorkerNumber-this.workerNum;
}
synchronized(workers){
initializeWorker(num);
this.workerNum+=num;
}
}
@Override
public void removeWorker(int num) {
if(num>=this.workerNum){
throw new IllegalArgumentException("beyond workNum");
}
synchronized(workers){
//按照给定的数量停止Worker
int count=num;
while(count>0){
Worker worker=workers.get(0);
if(workers.remove(worker)){
worker.shutdown();
count--;
}
}
this.workerNum-=num;
}
}
@Override
public int getJobSize() {
return jobs.size();
}
//工作者线程消费任务
public class Worker implements Runnable{
//是否工作
private volatile boolean running=true;
@Override
public void run() {
while(true){
Job job=null;
synchronized(jobs){
//如果任务队列是空的,那么就wait
while(jobs.isEmpty()){
if(running==false){
System.out.println(Thread.currentThread().getName()+"结束");
return;
}
try {
jobs.wait();
} catch (InterruptedException e) {
//感知外部对WorkerThread的中断操作,返回
Thread.currentThread().interrupt();
return;
}
}
if(running==false){
System.out.println(Thread.currentThread().getName()+"结束");
return;
}
//取出一个Job
job=jobs.removeFirst();
}
if(job!=null){
try{
System.out.println(Thread.currentThread().getName()+" 执行任务 "+job.toString());
job.run();
}catch(Exception e){
//忽略Job执行中的Exception
}
}
}
}
//关闭当前工作者
public void shutdown(){
running=false;
System.err.println("关闭一个工作者线程");
}
}
}
public class ThreadLocalTest {
//创建一个Integer型的线程本地变量
public static final ThreadLocal<Integer> local = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0;
}
};
//计数
static class Counter implements Runnable{
@Override
public void run() {
//获取当前线程的本地变量,然后累加5次
int num = local.get();
for (int i = 0; i < 100; i++) {
num++;
}
//重新设置累加后的本地变量
local.set(num);
System.out.println(Thread.currentThread().getName() + " : "+ local.get());
}
}
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[5];
for (int i = 0; i < 5; i++) {
threads[i] = new Thread(new Counter() ,"CounterThread-[" + i+"]");
threads[i].start();
}
}
}
输出:
.......
ThreadPool-Worker-1 执行任务 MyJob477
MyJob473任务执行完成!
ThreadPool-Worker-4 执行任务 MyJob478
MyJob474任务执行完成!
ThreadPool-Worker-5 执行任务 MyJob479
MyJob475任务执行完成!
ThreadPool-Worker-3 执行任务 MyJob480
MyJob476任务执行完成!
ThreadPool-Worker-2 执行任务 MyJob481
MyJob477任务执行完成!
ThreadPool-Worker-1 执行任务 MyJob482
MyJob478任务执行完成!
ThreadPool-Worker-4 执行任务 MyJob483
MyJob479任务执行完成!
ThreadPool-Worker-5 执行任务 MyJob484
关闭一个工作者线程
关闭一个工作者线程
关闭一个工作者线程
关闭一个工作者线程
关闭一个工作者线程
关闭线程池!
main线程执行时间:1047
MyJob481任务执行完成!
MyJob480任务执行完成!
ThreadPool-Worker-2结束
ThreadPool-Worker-3结束
MyJob482任务执行完成!
MyJob483任务执行完成!
ThreadPool-Worker-1结束
ThreadPool-Worker-4结束
MyJob484任务执行完成!
ThreadPool-Worker-5结束
MyJob473任务执行完成!
ThreadPool-Worker-4 执行任务 MyJob478
MyJob474任务执行完成!
ThreadPool-Worker-5 执行任务 MyJob479
MyJob475任务执行完成!
ThreadPool-Worker-3 执行任务 MyJob480
MyJob476任务执行完成!
ThreadPool-Worker-2 执行任务 MyJob481
MyJob477任务执行完成!
ThreadPool-Worker-1 执行任务 MyJob482
MyJob478任务执行完成!
ThreadPool-Worker-4 执行任务 MyJob483
MyJob479任务执行完成!
ThreadPool-Worker-5 执行任务 MyJob484
关闭一个工作者线程
关闭一个工作者线程
关闭一个工作者线程
关闭一个工作者线程
关闭一个工作者线程
关闭线程池!
main线程执行时间:1047
MyJob481任务执行完成!
MyJob480任务执行完成!
ThreadPool-Worker-2结束
ThreadPool-Worker-3结束
MyJob482任务执行完成!
MyJob483任务执行完成!
ThreadPool-Worker-1结束
ThreadPool-Worker-4结束
MyJob484任务执行完成!
ThreadPool-Worker-5结束
一个基于线程池技术的简单Web服务器
目前的浏览器都支持多线程访问,比如说在请求一个HTML页面的时候,页面中包含的图片资源、样式资源会被浏览器发起并发的获取,这样用户就不会遇到一直等到一个图片完全下载完成才能继续查看文字内容的尴尬情况。
如果Web服务器是单线程的,多线程的浏览器也没有用武之地,因为服务端还是一个请求一个请求的顺序处理。因此,大部分Web服务器都是支持并发访问的。常用的Java Web服务器,如Tomcat、Jetty,在其处理请求的过程中都使用到了线程池技术。
如果Web服务器是单线程的,多线程的浏览器也没有用武之地,因为服务端还是一个请求一个请求的顺序处理。因此,大部分Web服务器都是支持并发访问的。常用的Java Web服务器,如Tomcat、Jetty,在其处理请求的过程中都使用到了线程池技术。
下面通过使用前面实现的线程池来构造一个简单的Web服务器,这个Web服务器用来处理HTTP请求,目前只能处理简单的文本和JPG图片内容。这个Web服务器使用main线程不断地接受客户端Socket的连接,将连接以及请求提交给线程池处理,这样使得Web服务器能够同时处理多个客户端请求。
public class SimpleHttpServer {
// 处理HttpRequest的线程池
static ThreadPool<HttpRequestHandler> threadPool =
new DefaultThreadPool<HttpRequestHandler>(1);
// SimpleHttpServer的根路径
static String basePath;
static ServerSocket serverSocket;
// 服务监听端口
static int port = 8080;
public static void setPort(int port) {
if (port > 0) {
SimpleHttpServer.port = port;
}
}
public static void setBasePath(String basePath) {
if (basePath != null && new File(basePath).exists() &&
new File(basePath).isDirectory()) {
SimpleHttpServer.basePath = basePath;
}
}
// 启动SimpleHttpServer
public static void start() throws Exception {
serverSocket = new ServerSocket(port);
Socket socket = null;
while ((socket = serverSocket.accept()) != null) {
// 接收一个客户端Socket,生成一个HttpRequestHandler,放入线程池执行
threadPool.execute(new HttpRequestHandler(socket));
}
serverSocket.close();
}
//HttpRequest处理器
static class HttpRequestHandler implements Runnable{
private Socket socket;
public HttpRequestHandler(Socket socket){
this.socket=socket;
}
@Override
public void run() {
String line = null;
BufferedReader br = null;
BufferedReader reader = null;
PrintWriter out = null;
InputStream in = null;
try {
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String header = reader.readLine();
// 由相对路径计算出绝对路径
String filePath = basePath + header.split(" ")[1];
out = new PrintWriter(socket.getOutputStream());
// 如果请求资源的后缀为jpg或者ico,则读取资源并输出
if (filePath.endsWith("jpg") || filePath.endsWith("ico")) {
in = new FileInputStream(filePath);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int i = 0;
while ((i = in.read()) != -1) {
baos.write(i);
}
byte[] array = baos.toByteArray();
out.println("HTTP/1.1 200 OK");
out.println("Server: Molly");
out.println("Content-Type: image/jpeg");
out.println("Content-Length: " + array.length);
out.println("");
socket.getOutputStream().write(array, 0, array.length);
} else {
br = new BufferedReader(new InputStreamReader(new
FileInputStream(filePath)));
out = new PrintWriter(socket.getOutputStream());
out.println("HTTP/1.1 200 OK");
out.println("Server: Molly");
out.println("Content-Type: text/html; charset=UTF-8");
out.println("");
while ((line = br.readLine()) != null) {
out.println(line);
}
}
out.flush();
} catch (Exception ex) {
out.println("HTTP/1.1 500");
out.println("");
out.flush();
} finally {
close(br, in, reader, out, socket);
}
}
// 关闭流或者Socket
private static void close(Closeable... closeables) {
if (closeables != null) {
for (Closeable closeable : closeables) {
try {
closeable.close();
} catch (Exception ex) {
}
}
}
}
}
}
内容源自:
《Java并发编程的艺术》
《Java并发编程的艺术》