前言:
调用一个方法等待一段时间,如果在给定时间内得到结果,则立刻返回;否则返回默认值。这种模式就是等待超时模式。
等待超时模式的实现:
需要对等待/通知模式做出改动
假设超时时间是T,那么可以推断出在当前时间now+T之后就会超时。
定义如下变量:
等待持续时间:REMAINGING = T
超时时间:FUTURE = now + T
伪代码:
public synchronized Object get(long mills) {
long future=System.currentTimeMillis()+mills;
long remaining=mills;
while((result==null)&&remaining>0){
wait(remaining);
remaining=future-System.currentTimeMillis();
}
return result;
}
当已经获得结果或者剩余时间小于等于0,那么直接返回结果。
等待超时模式在等待/通知基础上增加了超时控制,使得该模式比原有范式更具有灵活性。
因为即使方法执行时间过长,也不会“永久”阻塞调用者,而是会按照调用者的要求按时返回。
一个简单的数据库连接池示例:
public class ConnectionPool {
/*
Connect数据库连接对象
pool:数据库连接池,用双端链表实现,里面放Connection对象
*/
private LinkedList<Connection>pool=new LinkedList<Connection>();
/*
初始化数据库连接池,即在数据库连接池中添加给定个Connection对象
* */
public ConnectionPool(int initialSize){
if(initialSize>0){
for(int i=0;i<initialSize;i++){
pool.addLast(ConnectionDriver.createConnection());
}
}
}
/*
* 释放连接
* ?为什么释放连接要addLast到pool中?
* */
public void releaseConnection(Connection connection){
if(connection!=null){
//pool是线程共享的,访问pool的时候要加锁
//为什么要对pool进行加锁,用其他对象作为锁可以吗?
synchronized (pool){
pool.addLast(connection);
//连接释放后需要进行通知,这样其他消费者能够感知连接池中已经归还了一个连接
pool.notifyAll();
}
}
}
//获取一个连接对象,如果在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(获取不到结果&&remaining>0){
* 锁对象.wait(remaining);
* remaining=future-当前时间;
* }
* result=null;
* if(获取到对象)result=get();
* return result;
* */
while(pool.isEmpty()&&remaining>0){
/*
* 执行完pool.wait()之后怎么办?
* */
pool.wait(remaining);
remaining=future-System.currentTimeMillis();
}
Connection result=null;
if(!pool.isEmpty()){
result=pool.removeFirst();
}
return result;
}
}
}
}
/*
* 动态代理:
* (1)InvocationHandler接口,重写invoke方法
* (2)Proxy.newProxyInstance();创建代理类对象
* */
static class ConnectionHandler implements InvocationHandler{
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName().equals("commit")){
TimeUnit.MILLISECONDS.sleep(100);
}
return null;
}
}
public static final Connection createConnection(){
return (Connection) Proxy.newProxyInstance(ConnectionDriver.class.getClassLoader(),new Class<?>[]{Connection.class},new ConnectionHandler());
}
}
public class ConnectionPoolTest {
//数据库连接池
static ConnectionPool pool=new ConnectionPool(10);
//保证所有ConnectionRunner线程能够同时开始运行
static CountDownLatch start=new CountDownLatch(1);
//end计数器为线程个数
static CountDownLatch end;
public static void main(String[] args) throws InterruptedException {
int threadCount=20;
//end计数器为线程数量
end=new CountDownLatch(threadCount);
int count=20;
//got和notGot是线程共享的变量,所有线程都可以对其进行修改。
//但是是线程安全的整数
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");
thread.start();
}
//所有线程同时开始运行
start.countDown();
//知道所有线程运行完后才能继续运行主线程
end.await();
System.out.println("total invoke:"+(threadCount*count));
System.out.println("got connection"+got);
System.out.println("not got connection"+notGot);
}
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()的作用是在main线程中创建threadCount个线程,它们运行start.await()后
// 会马上进入等待状态
/*
* 在创建完所有线程后start.countDown()使start的计数器变为0
* 这时所有线程同时开始运行。
* */
start.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
//一共尝试获取count次Connection对象
while(count>0){
try {
//从线程池中获取连接,如果1000ms内无法获取到,返回null
//分别统计连接获取数量got和未获取到的数量notGot
Connection connection = pool.fetchConnection(1000);
if (connection != null) {
try {
//模拟事务
connection.createStatement();
connection.commit();
} finally {
//释放connection对象,把connection对象放入连接池
pool.releaseConnection(connection);
//got自增1
got.incrementAndGet();
}
} else {
//如果获取不到notGot自增1
notGot.incrementAndGet();
}
}catch (Exception exception){
}
finally {
//次数减1
count--;
}
}
//执行完一个线程end减1,因为end等于线程数,所以当线程全部执行完后end计数器变为变为0
end.countDown();;
}
}
}
统计规律:
在资源一定的情况下,随着客户端线程的逐步增加,客户端出现超时无法获取连接的比率不断升高。
虽然客户端线程在超时获取的模式下会出现连接无法获取的情况,但是能够保证客户端线程不会一直挂载连接获取的操作上,而是按时返回。
数据库连接池的设计也可以复用到其他的资源获取的场景,针对昂贵资源的获取都应该加以超时限制。