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