线程池高级

一.估算线程池最优大小:
Ncpu = CPU的数量 = Runtime.getRuntime().availableProcessors();
Ucpu = 目标CPU的使用率, 0 <= Ucpu <= 1;
W/C = 等待时间与计算时间的比率;
为了保持处理器达到期望的使用率,最优的线程池大小等于:
Nthreads = Ncpu * Ucpu * (1+ W/C);

二.配置ThreadPoolExecutor:
ThreadPoolExecutor是Executors中工厂方法newCachedThreadPool、newFixedThreadPool和newScheduledThreadExecutor返回的ExecutorService接口的基本实现,提供了默认的执行策略。
ThreadPoolExecutor还允许通过构造方法定制执行策略,常用构造方法如下:

public ThreadPoolExecutor(int corePoolSize,   
                            int maximumPoolSize,   
                            long keepAliveTime,  
                            TimeUnit unit,   
                            BlockingQueue<Runnable> workQueue,  
                            ThreadFactory threadFactory,      
                            RejectedExecutionHandler handler){......}  

ThreadPoolExecutor配置执行策略的参数如下:
(1).核心池大小:
核心池大小是目标线程池大小,线程池实现试图维护池的大小,即使没有任务执行,池的大小也等于核心池的大小,并且直到工作队列充满前池都不会创建更多的线程。
如果核心池大小设置为0,则在工作队列填满之前线程池不会开始工作;若有一个有限的线程池和一个有限的工作队列,同时又希望所有的线程在没有任务的情况下销毁,则可以将核心线程池大小设置为0来激活这个特性,newCachedThreadPool的核心池大小就是0。

(2).最大池大小:
最大池大小是可同时活动的线程数的上限,若空闲时,会销毁超出核心池大小的多余线程。
newCachedThreadPool将最大池设置为Integer.MAX_VALUE。

(3).存活时间:
即空闲线程超时时间,若一个线程已经闲置的时间超过了存活时间,它将成为一个被回收的候选者,若当前的池大小已经超过了核心池大小,这个线程会被终止掉。
newFixedThreadPool为请求的线程池设置了核心池和最大池的大小,而且存活时间永远不会超时。
newCachedThreadPool默认的超时时间为1分钟。

(4).任务阻塞队列:
当请求到达速度超了线程池处理速度时,线程池将尚未开始执行的任务放入任务阻塞队列中等待。
任务阻塞队列通常有以下3中:
A.无限队列:
newFixedThreadPool和newSingleThreadExecutor默认使用一个无限的LinkedBlockingQueue阻塞队列,若所有工作者线程都处于忙碌,任务将会在队列中等候,若任务持续快速地到达,队列也会无限制地增加。
B.有限队列:
为了避免资源被耗尽,线程池也经常使用有限队列,如ArrayBlockingQueue或有界的LinkedBlockingQueue;当队列满时,新来的任务会使用饱和策略处理。
C.同步移交:
同步移交完全绕开队列,直接将任务从生产者移交给工作者线程。
同步移交适合线程池无限或者可以接受任务被拒绝的情况,newCachedThreadPool就使用同步移交方式。
注意:只有任务彼此独立时,使用有限线程池或有限工作队列才是合理的,若任务之间相互依赖,有限的线程池或工作队列就可能引起线程的饥饿死锁,而使用无限的线程池配置(newCachedThreadPool)可以避免任务相互依赖引起的线程饥饿死锁。

(5).饱和策略:
饱和策略用于规定有界队列充满或线程池被关闭时对新提交任务的处理方案。
JDK提供了如下4中饱和策略:
A.中止(abort):
默认的饱和策略,会抛出未检查的拒绝执行异常RejectedExecutionException,调用者捕获该异常做适当处理。
B.遗弃(discard):
丢弃最新提交的任务。
C.遗弃最旧的(discard-oldest):
丢弃本应该接下来就要执行的任务,并尝试去重新提交新任务,若是优先级队列,则丢弃优先级最高的任务,因此不能混合使用遗弃最旧的饱和策略和优先级队列。
D.调用者运行(caller-runs):
既不会丢最新提交的任务,也不会抛出异常,会把最新提交的任务推回到调用者,由生产者线程执行,一方面给工作者线程时间来追赶进度,另一方面减慢了生产者提交新任务的速度。
通过调节核心池大小和存活时间,可以促进线程池归还空闲线程占用的资源,饱和策略用于应对过载处理。这里可以注意一个线程池的面试

三.定制线程工厂:
线程池通过线程工厂来创建线程,默认的线程池工厂会创建一个新的,非后台的线程,没有特殊配置。
线程池工厂可以运行定制线程的配置信息,例如:为线程池指定UncaughtExceptionHandler,用于捕获线程抛出的未检查异常以防止线程泄露;实例化一个定制的线程类实例,用来执行调试日志线程;给线程指定名称等等。
线程池工厂只有一个newThread方法,在线程池需要创建一个新线程时使用,例子代码如下:

public class MyThreadFactory implements ThreadFactory{  
    private final String poolName;  

    public MyThreadFactory(String poolName){  
        this.poolName = poolName;  
    }  

    public Thread newThread(Runnable runnable){  
        return new MyAppThread(runnable, poolName);  
    }  
}  

public class MyAppThread extends Thread{  
    public static final String DEFAULT_NAME = “MyAppThread”;  
    private static volatile boolean debug = false;  
    private static final AtomicInteger created = new AtomicInteger();  
    private static final AtomicInteger alive = new AtomicInteger();  
    private static final Logger log = Logger.getLogger(MyAppThread.class.getClassName());  

    public MyAppThread(Runnable r){  
        this(r, DEFAULT_NAME);  
    }  

    public MyAppThread(Runnable r, String name){  
        super(runnable, name + “-” + created.incrementAndGet());  
        setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler(){  
            public void uncaughtException(Thread t, Throwable e){  
                log.log(Level.SEVER, “Uncaught in thread ” + t.getName(), e);  
            }  
        });  
    }  

    public void run(){  
        if(debug){  
            log.log(Level.FINE, “Created ” + t.getName());  
        }  
        try{  
            alive.incrementAndGet();  
            super.run();  
        }finally{  
            alive.decrementAndGet();  
            if(debug){  
                log.log(Level.FINE, “Exiting ” + t.getName());  
            }  
        }  
    }  

    public static int getThreadsCreated(){  
        return created.get();  
    }  

    public static int getThreadsAlive(){  
        return alive.get();  
    }  

    public static boolean isDebug(){  
            return debug;  
    }  

    public static void setDebug(boolean debug){  
        this.debug = debug;  
    }  
}  

四.扩展ThreadPoolExecutor:
ThreadPoolExecutor提供了以下3个生命周期的钩子方法让子类扩展:
(1).beforeExecute:
任务执行前,线程会调用该方法,可以用来添加日志、监控或者信息收集统计。
若beforeExcute方法抛出了RuntimeException,线程的任务将不被执行,afterExecute方法也不会被调用。

(2).afterExecute:
任务执行结束后,线程会调用该方法,可以用来添加日志、监控或者信息收集统计。
无论任务正常返回或者抛出异常(抛出Error不能被调用),该方法都会被调用。

(3).terminate:
线程池完成关闭动作后调用,可以用来释放资源、发出通知、记录日志或者完成统计信息等。
一个扩展ThreadPoolExecutor的例子代码如下:

public class TimingThreadPool extends ThreadPoolExecutor{  
    private final ThreadLocal<Long> startTime = new ThreadLocal<Long>();  
    private final Logger log = Logger.getLogger(TimingThreadPool.class.getClassName());  
    private final AtomicLong numTasks = new AtomicLong();  
    private final AtomicLong totalTime = new AtomicLong();  

    protected void beforeExecute(Thread t, Runnable r){  
        super.beforeExecute(t, r);  
        log.fine(String.format(“Thread %s: start %s”, t, r));  
        startTime.set(System.nanoTime());  
    }  

    protected void afterExecute(Runnable r, Throwable t){  
        try{  
            long endTime = System.nanoTime();  
            long taskTime = endTime - startTime.get();  
            numTasks.incrementAndGet();  
            totalTime.addAndGet(taskTime);  
            log.fine(String.format("Thread %s: end %s, time=%dns", t, r, taskTime));  
        }finally{  
            super.afterExecute(r, t);  
        }  
    }  

    protected void terminated(){  
        try{  
            log.info(String.format("Terminated: avg time=%dns",   
                    totalTime.get() / numTasks.get()));  
        }finally{  
            super.terminated();  
        }  
    }  
}  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值