前言:
有好长一段时间没有写博客了,思来想去,懒是主要的原因,另外一方面笔者在年初换了工作,适应工作还是花了蛮长一段时间的,还有就是,没找到一个合适的主题、系列来写。
之前写的很多对个人的成长还是比较有益的,起码现在在看源码的时候不像前两年那么吃力了,写的代码也会刻意去模仿源码的风格来做。
现在找到一个合适的主题,就是Sentinel,因为公司未来要引入这项技术,所以笔者先前期研究下。
看代码多了的好处就是,大家处理问题的方式大都大同小异,优秀的方式大家都在相互借鉴。笔者也想把这些闪光点单独整理出来。所以暂时的这个系列不会像之前Spring那样给与整体框架分析,而是反过来,从细微处见真章。这样印象应该会更深刻些。当然在最后的时候也是要好好分析一下整体框架的。
注意:后面所使用的Sentinel版本为1.7-Realease版本
1.JDK中关于线程池的使用
线程池是我们大家都比较熟悉的一个点了,当我们想并发执行任务时,首先想到的就是开启多个线程来执行。
最简单的方式莫过于直接创建一个线程任务,如下所示:
new Thread(new Runnable() {
@Override
public void run() {
// do your task here ...
}
}).start();
这样直接创建线程的方式坏处也是显而易见的,当任务过多时,每个任务都创建一个线程,那么大量CPU时间都浪费在线程的切换,而且线程过多时也是会占用大量内存的,所以在实际使用时我们一般也不会采用这种方式(少量任务,新建一两个线程也是可以的)。
下面我们看下JDK提供的直接使用线程池的方案:
// 固定线程池 线程数量为10
ExecutorService executorService = Executors.newFixedThreadPool(10);
executorService.submit(new Runnable() {
@Override
public void run() {
// do your task here ...
}
});
我们比较常用上述方式来做,那么问题来了,这种方式有什么不好的嘛?
2.阿里代码规范关于线程池的描述
在阿里的代码规范中,有关于线程池的描述,如下
1. 线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
1)FixedThreadPool 和 SingleThreadPool: 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2)CachedThreadPool 和 ScheduledThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
直接来分析下这段代码:
// Executors.newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
// 关于LinkedBlockingQueue 的创建
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
// 可以看到容量基本是不限量,这样当消费速度过慢时,数据会不断的被写入链表,内存有被耗尽的风险
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
last = head = new Node<E>(null);
}
3.Sentinel中关于线程池的使用
Sentinel中有大量的线程池使用的情况,下面来截取一段代码(ZookeeperDataSource.java)
// ZookeeperDataSource.java中,在成员变量中定义了一个线程池
private final ExecutorService pool = new ThreadPoolExecutor(1, 1, 0, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(1), new NamedThreadFactory("sentinel-zookeeper-ds-update"),
new ThreadPoolExecutor.DiscardOldestPolicy());
// NamedThreadFactory定义
public class NamedThreadFactory implements ThreadFactory {
private final ThreadGroup group;
// 自增线程序列号
private final AtomicInteger threadNumber = new AtomicInteger(1);
// 可以自定义线程名称
private final String namePrefix;
// 定义是否守护线程
private final boolean daemon;
public NamedThreadFactory(String namePrefix, boolean daemon) {
this.daemon = daemon;
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
this.namePrefix = namePrefix;
}
public NamedThreadFactory(String namePrefix) {
this(namePrefix, false);
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r, namePrefix + "-thread-" + threadNumber.getAndIncrement(), 0);
t.setDaemon(daemon);
return t;
}
}
可以看到,Sentinel在创建线程池时
1.首先自定义一个ThreadFactory,(Executors会使用默认的DefaultThreadFactory,两个大同小异)。
2.通过手动new 一个 ThreadPoolExecutor,自定义其中的参数来创建线程池,我们来分析下这个线程池的参数:
corePoolSize:1
maxPoolSize:1
workQueue:ArrayBlockQueue(1) // 长度为1,通过控制这个长度,来放置内存溢出
rejectPolicy:discardOldestPolicy // 抛弃最先的一个任务
总结:
以后我们在创建线程池的时候,尽量避免直接使用Executors来创建线程池。
通过自定义一个ThreadPoolExecutor来创建线程池,自定义其中的参数即可。
参考:https://github.com/alibaba/Sentinel