Sentinel源码-线程池的使用

前言:

    有好长一段时间没有写博客了,思来想去,懒是主要的原因,另外一方面笔者在年初换了工作,适应工作还是花了蛮长一段时间的,还有就是,没找到一个合适的主题、系列来写。

    之前写的很多对个人的成长还是比较有益的,起码现在在看源码的时候不像前两年那么吃力了,写的代码也会刻意去模仿源码的风格来做。

    现在找到一个合适的主题,就是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 

发布了122 篇原创文章 · 获赞 119 · 访问量 12万+

猜你喜欢

转载自blog.csdn.net/qq_26323323/article/details/103515091