Java線程池的使用

設置線程池的大小

線程池的理想大小取決於被提交任務的類型以及所部署的系統。通常根據某種配置機制來提供。

對於計算密集型的任務,在擁有N_{cpu}個處理器的系統上,當線程池的大小爲N_{cpu+1}時,通常能實現最優的利用率。

對於包含I/O操作或者其他阻塞操作的任務,由於線程並不會一直執行,因此線程池的規模應該更大。

                                     N_{cpu}=number of CPUs

                                     U_{cpu}=target CPU utilization, 0 ≤U_{cpu}≤1

                                   \frac{W}{C}=ratio of wait time to compute time

要使處理器達到期望的使用率,線程池的最優大小等於:

                               N_{threads}=N_{cpus}*U_{cpu}*(1+\frac{W}{C})

可以通過Runtime獲得CPU的數目:

int N_CPU = Runtime.getRuntime().availableProcessors();

配置ThreadPoolExecutor

ThreadPoolExecutor爲一些Executor提供了基本的實現:Executor框架與Java線程池

飽和策略

當有界隊列被填滿後,飽和策略開始發揮作用。通過setRejectedExecutionHandler來修改。

AbortPolicy: 默認策略,拋出未檢查的RejectedExecutionException供調用者捕獲。

DiscardPolicy: 悄悄拋棄該任務。

DiscardOldestPolicy: 拋棄下一個將被執行的任務(對於優先隊列,將是優先級最高的那個任務),然後嘗試重新提交新的任務。

CallerRunsPolicy: 既不拋棄任務,也不拋出異常,而是將某些任務回退到調用者,從而降低新任務的流量。當工作隊列被填滿後,下一個任務會在調用execute時在主線程中執行。由於執行任務需要一定的時間,因此主線程至少在一段時間內不能提交任務,從而使得工作者線程有時間來處理完正在執行的任務。

 示例代碼:

ThreadPoolExecutor executor = new ThreadPoolExecutor(N_THREADS, N_THREADS, 0L,
                TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(CAPACITY));
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

使用Semaphore限制任務的到達率:

public class BoundedExecutor {
    private final Executor exec;
    private final Semaphore semaphore;

    public BoundedExecutor(Executor exec, int bound) {
        this.exec = exec;
        this.semaphore = new Semaphore(bound);
    }

    public void submitTask(final Runnable command) throws InterruptedException {
        semaphore.acquire();
        try {
            exec.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        command.run();
                    } finally {
                        semaphore.release();
                    }
                }
            });
        } catch (Exception e) {
            semaphore.release();
        }
    }
}

每當線程需要創建一個線程時,都是通過線程工廠方法來完成的。可以爲每個線程指定UncaughtExceptionHandler,或者實例化一個定製的Thread類用於執行調試信息的記錄:

public class MyThreadFactory implements ThreadFactory {
    private final String poolName;

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

    @Override
    public Thread newThread(Runnable r) {
        return new MyAppThread(r, poolName);
    }
}

public class MyAppThread extends Thread {
    public static final String DEFAULT_NAME = "MyAppThread";
    private static final AtomicInteger created = new AtomicInteger();
    private static final AtomicInteger alive = new AtomicInteger();
    private static final Logger logger = Logger.getAnonymousLogger();
    private static volatile boolean debugLifeCycle = false;

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

    public MyAppThread(Runnable target, String name) {
        super(target, name + "-" + created.incrementAndGet());
        setUncaughtExceptionHandler(
                new Thread.UncaughtExceptionHandler() {
                    @Override
                    public void uncaughtException(Thread t, Throwable e) {
                        logger.log(Level.SEVERE, "UNCAUGHT in thread " + t.getName(), e);
                    }
                }
        );
    }

    @Override
    public void run() {
        //複製debug標誌以確保一致的值??
        boolean debug = debugLifeCycle;
        if (debug) logger.log(Level.FINE, "Created " + getName());
        try {
            alive.incrementAndGet();
            super.run();
        } finally {
            alive.decrementAndGet();
            if (debug) logger.log(Level.FINE, "Exiting " + getName());
        }
    }
    
    public static int getThreadCreated() { return created.get();}
    public static int getThreadAlive() { return alive.get();}
    public static boolean getDebug() { return debugLifeCycle; }
    public static void setDebug(boolean debug) { debugLifeCycle = debug;}
}

擴展ThreadPoolExecutor

ThreadPoolExecutor是可以擴展的,它提供了幾個可以在子類中改寫的方法:beforeExecute、afterExecute和terminated,這些方法可以用於擴展ThreadPoolExecutor的行爲。

示例代碼:

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

    public TimingThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        super.beforeExecute(t, r);
        logger.fine(String.format("Thread %s: start %s", t, r));
        startTime.set(System.nanoTime());
    }

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

    @Override
    protected void terminated() {
        try {
            logger.info(String.format("Terminated: avg time=%dns", totalTime.get() / numTasks.get()));
        } finally {
            super.terminated();
        }
    }
}

參考:《Java併發編程實戰》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章