java高并发之多线程踩坑

         我们都知道在java语言中,开启线程是继承Thead或者实现Runnable接口,在高级一点我们使用Thread线程池。接下来,我要说的就是Thread线程池踩坑。
         首先,需要注意的是什么场景需要使用线程池,使用哪个线程池,使用线程池需要注意哪些地方。
         我们使用多线程是为了让多核cpu处理器更好的发光发热。如果是单核cpu开多了线程也不会有实际效果。所以我们在使用多线程要考虑机器的配置。一般情况,我们开cpu的2到3倍线程,开多了会增加系统调度开销处理不过来,开少了不能充分利用cpu。实际上,如果是耗时高的任务,可以这样处理。耗时短的比如过io操作,我们可以多开一些线程的。 还有,我们在服务中可以自定义一个全局线程池,这样子,可以更加方便的管理,更好的控制服务线程的数量。但是,如果有不同场景,比如耗时长的和耗时短的还是应该分成两个线程池单独管理比较好。下面我们看一下线程池使用方式。

线程池使用方式:

线程池需要实现Executor接口,划重点(线程池大小,队列长度和拒绝策略)
### 1、使用excutors类定义(不够灵活)


Executors.defaultThreadFactory();  //默认工厂 
Executors.newCachedThreadPool();   //根据需要无限阔成
Executors.newFixedThreadPool();    //创建固定数量线程池
Executors.newScheduledThreadPool() //创建定时器线程池

2、使用ThreadPoolExecutor或者AsyncTaskExecutor(spring)自定义线程池

(1)ThreadPoolExecutor


private static final ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 5, 60L, TimeUnit.MINUTES,
        new LinkedBlockingQueue<>(1000), new ThreadPoolExecutor.CallerRunsPolicy());
还有其他构造方式就不一一介绍了

(2)使用Spring线程池AsyncTaskExecutor


@Configuration
@EnableAsync
public class ThreadPoolConfiguration {

    @Bean
    @Primary
    public AsyncTaskExecutor asyncServiceExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //核心线程数量:低于此值会重新创建
        executor.setCorePoolSize(5);
        //任务队列:核心线程处于工作中,会先放到队列中缓存
        executor.setQueueCapacity(1000);
        //线程池最大数量:任务队列已满时,最多创建的线程数量
        executor.setMaxPoolSize(100);
        //线程超时时间:超出核心线程数量的线程,在空闲一定的时间后退出
        executor.setKeepAliveSeconds(30);
        //线程池已满,队列已满时。新的任务处理策略:丢弃、执行、忽略、
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return executor;
    }
}

3、我们自定义线程池工厂类


/**
 * The default thread factory
 */
static class DefaultThreadFactory implements ThreadFactory {
    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    private final ThreadGroup group;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;

    DefaultThreadFactory() {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() :
                              Thread.currentThread().getThreadGroup();
        namePrefix = "pool-" +
                      poolNumber.getAndIncrement() +
                     "-thread-";
    }

    public Thread newThread(Runnable r) {
        Thread t = new Thread(group, r,
                              namePrefix + threadNumber.getAndIncrement(),
                              0);
        if (t.isDaemon())
            t.setDaemon(false);
        if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);
        return t;
    }
}

这是线程池默认工厂类,我们可以把它重写自定义,一般就是修改一下名字,日志啥的。

4、线程池拒绝策略

我们在使用的时候,一般只注意线程的数量,最大线程数量,线程存活时间参数。 忘掉最重要两个参数,  
阻塞队列和线程池拒绝策略。ThreadPoolExecutor线程池队列默认是无界队列如果我们不设置长度,  
极端情况会把我们系统给撑死。ok,为了不把服务累死,我们把队列长度设置了,

此时,拒绝策略开始发挥作用了。默认拒绝策略是 AbortPolicy:这样当线程池满了,队列满了,  
会把任务丢弃适合允许丢失追求性能的场景。

//线程池已满,队列已满时。新的任务处理策略:丢弃、执行、忽略 

//AbortPolicy:丢弃  
//DiscardPolicy:忽略  
//CallerRunsPolicy:立即运行  
//DiscardOldestPolicy:压进队列最后一位(踢出第一位)

切记不用忘了设置队列长度,和拒绝策略防止数据丢失

个人博客地址


wotrd
240 声望36 粉丝

有术无道,止于术;有道无术,有术可求。