- Published on
ARTS 第20周
- Authors
- Name
- Jason Yang
- @yangjinlong86
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)
- 估算出任务的等待时间于计算时间的比值