线程池之深度了解-ThreadPoolExecutor

来源:互联网 发布:淘宝银座365是正品吗 编辑:程序博客网 时间:2024/05/21 02:52

代码的世界和现实的世界还真是像,解决办法也真一样。


最近遇到个交通阻塞的问题。


场景:

服务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语句块里面,尝试(总是成功)启动新的线程去执行此任务。




代码里面的坑太多,需要一个一个去填平!








0 0