Published on

ARTS 第20周

Authors

Algorithm

package org.nocoder.leetcode.solution;

import java.util.TreeMap;

/**
 * 933. Number of Recent Calls
 *
 * Write a class RecentCounter to count recent requests.
 *
 * It has only one method: ping(int t), where t represents some time in milliseconds.
 *
 * Return the number of pings that have been made from 3000 milliseconds ago until now.
 *
 * Any ping with time in [t - 3000, t] will count, including the current ping.
 *
 * It is guaranteed that every call to ping uses a strictly larger value of t than before.
 *
 *
 *
 * Example 1:
 *
 * Input: inputs = ["RecentCounter","ping","ping","ping","ping"], inputs = [[],[1],[100],[3001],[3002]]
 * Output: [null,1,2,3,3]
 *
 *
 * Note:
 *
 * Each test case will have at most 10000 calls to ping.
 * Each test case will call ping with strictly increasing values of t.
 * Each call to ping will have 1 <= t <= 10^9.
 *
 * @author jason
 * @date 2018/11/18.
 */
public class NumbersOfRecentCalls {
	
    class RecentCounter {

    	TreeMap<Integer, Integer> tm;
    	
        public RecentCounter() {
        	tm = new TreeMap<>();
        }

        public int ping(int t) {
        	tm.put(t, 1 + tm.size());
            return tm.tailMap(t - 3000).size();
        }
    }

/**
 * Your RecentCounter object will be instantiated and called as such:
 * RecentCounter obj = new RecentCounter();
 * int param_1 = obj.ping(t);
 */
}


Review

Java Thread Pool Example using Executors and ThreadPoolExecutor

线程池包含一个队列,用于保持任务等待执行。线程池管理Runnable线程的集合,工作线程从队列中执行Runnable。java.util.concurrent.Executors提供java.util.concurrent.Executor接口的实现,以在java中创建线程池。

Tip

线程池的饱和策略

ThreadPoolExecutor 构造方法中包含一个 BlockingQueue 参数,这个队列接收任务,并在线程中执行,如果这个队列满了,就会在创建一个新的线程,将任务放入新线程的Queue中,当线程数量达到线程池大小上限之后,线程池会reject 这个任务,此时 RejectedExceptionHandler 就派上用场了。

饱和策略:当队列满了之后,饱和策略开始发挥作用,ThreadPoolExecutor 的饱和策略可以通过调用 RejectExceptionHandler 来修改。

JDK 提供了四种RejectExceptionHandler实现:AbortPolicy,CallerRunsPolicy,DiscardPolicy,DiscardOldestPolicy。

Abort: 默认的饱和策略,该策略会抛出RuntimeException

Discard:抛弃该任务

Discard-Oldest:抛弃下一个奖杯执行的任务,然后尝试重新提交的新任务

Caller-Runs:实现了一种调节机制,不会抛弃任务,也不抛出异常,而是将任务退回到调用者,优调用者来执行,从而降低新任务的流量。

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

executor.setRejectExceptionHandler(new ThreadPoolExecutor.CallRunsPolicy());

Share

正确设置线程池的大小

最佳的线程池的大小,取决于被执行的任务和系统硬件。如果线程池过大,大量的线程将在相对很少的CPU和内存资源上发生竞争,这样会导致更多的内存消耗;如果线程池过小,许多空闲的处理器没有得到利用,降低了吞吐量。

正确的设置线程池的大小,必须分析系统环境,资源和任务的特性,有多少个CPU,多大的内存,任务是计算密集型还是IO密集型,还是二者皆可。

如果需要执行不同的任务,并且它们之间的行为相差很大,那么应该考虑使用多个线程池,从而使每个线程池可以根据各自的工作负载来调整。

  • 对于计算密集型的任务
    • N个CPU,线程池的大小为N+1时,通常能实现最优的利用率。
  • 对于包含IO操作或者其他阻塞操作的任务,由于线程并不会一直执行,因此线程池的规模应该更大
    • 估算出任务的等待时间于计算时间的比值
      • 在某个基准负载下,分别设置不同大小的线程池来运行程序,观察CPU利用率
      • N = CPU的数量(可以通过Runtime.getRuntime().availableProcessors()获取CPU数量)
      • U = CPU的使用率(0 <= U <= 1)
      • W/C = 计算等待时间 / 计算时间
      • 线程池的最优大小 = N * U * (1 + W/C)