并发编程

一、多线程的使用

1、在阿里巴巴Java开发手册中也明确指出,不允许使用Executors创建线程池。
阿里巴巴java开发手册说明如下:
【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

说明:Executors返回的线程池对象的弊端如下:
FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
CachedThreadPool和ScheduledThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM

2、固定线程池:创建固定数目线程的线程池

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

单例线程池:创建一个单线程化的Executor

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

newFixedThreadPool和newSingleThreadExecutor的阻塞队列均使用的LinkedBlockingQueue,是一个用链表实现的阻塞队列,不设置容量的话,默认最大长度为Integer.MAX_VALUE,这样就不断的向队列中加入任务的,创建的最大线程数可能是Integer.MAX_VALUE,而创建这么多线程,必然就有可能导致OOM。

3、

public class ThreadPoolExecutorUtils {

    private static int CORE_POOL_SIZE = 10;
    private static int MAX_POOL_SIZE = 20;
    private static long KEEP_ALIVE_TIME = 30;
    private static int QUEUE_SIZE = 200;
    private static String THREAD_POOL_NAME = "thread-pool";
    private static volatile ExecutorService pool = null;

    private ThreadPoolExecutorUtils(){

    }
    public static ExecutorService getPool() {
        // 使用双重校验锁保证只有一个pool实例
        if (pool == null) {
            synchronized (ThreadPoolExecutorUtils.class) {
                if (pool == null) {
                    ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat(THREAD_POOL_NAME + "-%d").build();
                    pool = new ThreadPoolExecutor(CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE_TIME, TimeUnit.SECONDS,
                            new LinkedBlockingQueue<>(QUEUE_SIZE), namedThreadFactory,
                            new ThreadPoolExecutor.AbortPolicy());
                }
            }
        }
        return pool;
    }
}        

总结引用:https://mp.weixin.qq.com/s/kefLFjLTOZuwmklrMPAOLA

N核服务器,通过执行业务的单线程分析出本地计算时间为x,等待时间为y,则工作线程数(线程池线程数)设置为 N*(x+y)/x,能让CPU的利用率最大化。

一般来说,非CPU密集型的业务(加解密、压缩解压缩、搜索排序等业务是CPU密集型的业务),瓶颈都在后端数据库访问或者RPC调用,本地CPU计算的时间很少,所以设置几十或者几百个工作线程是能够提升吞吐量的。

二、面试题汇总

1、如何减少上下文切换(上下文切换是指cup的控制权由运行任务转移给就绪任务所发生的事件)

  多线程多锁资源的竞争造成cpu的上下文切换,

  1. 无锁并发编程:对数据ID进行hash取模分段,不同的线程处理不同段的数据;
  2. 使用非阻塞乐观锁代替竞争锁:CAS,java的Atomic包下使用CAS更新数据,不需要加锁;
  3. 合理设置线程池大小,避免创建过多线程;
  4. 使用协程实现非阻塞等待:在单线程内实现多任务调度,并在单线程内实现多任务切换。
  5. 锁:减少锁的持有时间,降低锁的粒度(锁分离、锁分段),优化wait、notify的使用;

 

posted @ 2019-12-17 19:16  提拉米苏007  阅读(189)  评论(0编辑  收藏  举报