异步模式之工作线程
定义
让有限的工作线程(Worker Thread)来轮流异步处理无限多的任务。也可以将其归类为分工模式,它的典型实现 就是线程池,也体现了经典设计模式中的享元模式。
注意,不同任务类型应该使用不同的线程池,这样能够避免饥饿,并能提升效率。
饥饿
固定大小线程池会有饥饿现象
两个工人是同一个线程池中的两个线程
他们要做的事情是:为客人点餐和到后厨做菜,这是两个阶段的工作
比如工人A 处理了点餐任务,接下来它要等着 工人B 把菜做好,然后上菜,他俩也配合的蛮好
但现在同时来了两个客人,这个时候工人A 和工人B 都去处理点餐了,这时没人做饭了,饥饿
问题提出
@Slf4j(topic = "c.StarvationDemo")
public class StarvationDemo {static final List<String> MENU = Arrays.asList("地三鲜","宫保鸡丁","辣子鸡丁","烤鸡翅");static Random RANDOM = new Random();static String coke(){return MENU.get(RANDOM.nextInt(MENU.size()));}public static void main(String[] args) {ExecutorService pool = Executors.newFixedThreadPool(2);pool.execute(()->{log.debug("处理点餐...");Future<String> f = pool.submit(() -> {log.debug("做菜");return coke();});try {log.debug("上菜: {}",f.get());} catch (InterruptedException | ExecutionException e) {e.printStackTrace();}});pool.execute(()->{log.debug("处理点餐...");Future<String> f = pool.submit(() -> {log.debug("做菜");return coke();});try {log.debug("上菜: {}",f.get());} catch (InterruptedException | ExecutionException e) {e.printStackTrace();}});}
}
运行该代码后会发现两个线程都停在了处理点餐,都在等待其它线程做菜。
问题解决
将不同的任务放入不同的线程池。
@Slf4j(topic = "c.StarvationDemo")
public class StarvationDemo {static final List<String> MENU = Arrays.asList("地三鲜","宫保鸡丁","辣子鸡丁","烤鸡翅");static Random RANDOM = new Random();static String coke(){return MENU.get(RANDOM.nextInt(MENU.size()));}public static void main(String[] args) {ExecutorService waiterPool = Executors.newFixedThreadPool(2);ExecutorService cookPool = Executors.newFixedThreadPool(2);waiterPool.execute(()->{log.debug("处理点餐...");Future<String> f = cookPool.submit(() -> {log.debug("做菜");return coke();});try {log.debug("上菜: {}",f.get());} catch (InterruptedException | ExecutionException e) {e.printStackTrace();}});waiterPool.execute(()->{log.debug("处理点餐...");Future<String> f = cookPool.submit(() -> {log.debug("做菜");return coke();});try {log.debug("上菜: {}",f.get());} catch (InterruptedException | ExecutionException e) {e.printStackTrace();}});}
}
创建多少线程池合适
过小会导致程序不能充分地利用系统资源、容易导致饥饿
过大会导致更多的线程上下文切换,占用更多内存
1.CPU密集型运算(一般做数据分析时属于CPU密集型运算)
通常采用 CPU 核数 + 1 能够实现最优的 CPU 利用率,+1 是保证当线程由于页缺失故障(操作系统)或其它原因导致暂停时,额外的这个线程就能顶上去,保证 CPU 时钟周期不被浪费
2.I/O密集型运算(web型应用程序)
CPU 不总是处于繁忙状态,例如,当你执行业务计算时,这时候会使用 CPU 资源,但当你执行 I/O 操作时、远程 RPC 调用时,包括进行数据库操作时,这时候 CPU 就闲下来了,你可以利用多线程提高它的利用率。
经验公式如下
线程数 = 核数 * 期望 CPU 利用率 * 总时间(CPU计算时间+等待时间) / CPU 计算时间
例如 4 核 CPU 计算时间是 50% ,其它等待时间是 50%,期望 CPU 被 100% 利用,套用公式
4 * 100% * 100% / 50% = 8
例如 4 核 CPU 计算时间是 10% ,其它等待时间是 90%,期望 CPU 被 100% 利用,套用公式
4 * 100% * 100% / 10% = 40
时间占比可以通过分析或监控工具来获得