代码的世界和现实的世界还真是像,解决办法也真一样。
最近遇到个交通阻塞的问题。
服务A 提供了两个接口,get和delete
两个接口共享一个出口(如同时最多有20个连接),当其中一个接口响应慢的时候,会把几乎全部的出口带宽占完,这样会导致原本正常的get接口得不到响应,客户端在调用的时候,总是连接不上。
看起来其实有点像家用的路由器似的,3个人共享上网,当其中一个人占用了很大的带宽的时候,会导致另外连个人不能正常上网。
也有点像路由器似的,可以根据不同的ip进行限制带宽。
服务A 也可以根据不同的接口限制多少的最大连接数
代码还原
由于项目用到了netty,里面有个executionhandler的概念,大意也就是用异步的方式(线程池)执行比较耗时的业务代码。
executeThreadPool = new ThreadPoolExecutor(minThreadPoolSize,//8
maxThreadPoolSize, 0L, TimeUnit.MILLISECONDS, // 200
new LinkedBlockingQueue<Runnable>(maxQueueBlockSize)); //1000
初步看起来感觉没什么问题,但真正运行起来后,会发现当并发数达到 20 个时候,发现进入业务代码里面的线程也就只有8个,而其余的12个是放在了queue里面了,等待核心线程去执行。
这就是问题所在,和我们的初衷并不一样(最多启动200个线程去执行客户的请求,多的时候在放到队列里面等待执行)
还是继续跟到ThreadPoolExecutor里面看吧
之前简单的写过
http://blog.csdn.net/chaofanwei/article/details/17286619
先看以下代码,感觉sun的工程师搞的挺好玩的,也算是脑洞大开啊
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3; //29
private static final int CAPACITY = (1 << COUNT_BITS) - 1; //536870911 00011111111111111111111111111111
// runState is stored in the high-order bits
private static final int RUNNING = -1 << COUNT_BITS; //-536870912 11100000000000000000000000000000
private static final int SHUTDOWN = 0 << COUNT_BITS; //0 00000000000000000000000000000000
private static final int STOP = 1 << COUNT_BITS; //536870912 00100000000000000000000000000000
private static final int TIDYING = 2 << COUNT_BITS; //1073741824 01000000000000000000000000000000
private static final int TERMINATED = 3 << COUNT_BITS; //1610612736 01100000000000000000000000000000
// Packing and unpacking ctl
private static int runStateOf(int c) { return c & ~CAPACITY; } //根据int c返回表示的状态
private static int workerCountOf(int c) { return c & CAPACITY; } //根据int c返回表示的线程个数
private static int ctlOf(int rs, int wc) { return rs | wc; } // 用指定的状态和线程个数初始化 int c
目的就是用一个int类型的数,前面3位用来标示当前线程池的状态(RUNNING,SHUTDOWN ...),后面的29位表示当前线程池里面的线程个数。
再来看核心的接口execute方法内部实现
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) { //如果当前线程数小于core线程数,则直接启动新的线程并把command当做线程的第一个任务来执行
if (addWorker(command, true)) //启动新线程,成功的话,直接返回
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) { //①
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0) //没有线程的话,则新启动一个线程
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
上面代码①意思
试图把command塞到队列里面,看是否能赛成功,这个地方根据queue的不同又分为两种情况,LinkedBlockingQueue和SynchronousQueue
LinkedBlockingQueue
应用场景就是
Executors.newFixedThreadPool(int nThreads, ThreadFactory threadFactory){
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
这种情况下,如果队列没有满的话,则直接塞到里面就返回true了,但并不会启动新的线程去执行此task,而是等待别的线程空闲下来时候再从队列里面取出来执行。
只有当此队列满了,offer返回失败,才会走到下面的else语句块里面,试图启动新的线程去执行。
一句话总结也就是优先启动corePoolSize个线程,然后多的任务放到队列里面等待corePoolSize的线程去执行,只有当队列满的时候,才会启动最多maximumPoolSize的线程去执行任务。
SynchronousQueue
应用场景就是
Executors.newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
SynchronousQueue用一句话来说就是相当于只能装一个entry的阻塞队列,也相当于一个transfer,即只有把offer进来的entry给传递到另外一个线程的时候才返回true(即如果刚好有另外一个线程在等待接收的时候才返回true)
这种情况下就是,如果offer成功了,就表示已经有另外一个线程在执行了,如果返回失败,表示所有的线程都在忙碌,就走到了下面的else语句块里面,尝试(总是成功)启动新的线程去执行此任务。
代码里面的坑太多,需要一个一个去填平!