Thread
原生线程并发难度在于理解力和想像力。new Thread(()->{
//to do
}).start();
Executors
Java并发领军人物Doug Lea杰作,Executors拥有众多应用解决方案。
1、固定数量的线程池:Executors.newFixedThreadPool(10);
没有空闲线程,新任务加入时,任务会被放入队列,直到有可用线程。
2、根据需要自动扩充线程数,并能重用已构造的线程。Executors.newCachedThreadPool();
对执行短暂异步任务的场景能明显提高性能。
调用execute时,有空闲线程则重用,没有则创建。空闲60秒从缓存中清除。
3、可指定延迟执行时间或定时周期Executors.newScheduledThreadPool(10);
4、单线程,任务被放入队列,执行出现异常,导致线程不能正常返回时,则创建新线程。Executors.newSingleThreadExecutor();
5、工作窃取线程池,线程间相互协作,共同完成任务,每个线程领取的任务数不固定。Executors.newWorkStealingPool();
ForkJoinPool
使用时需告诉ForkJoinPool如何分解任务。
如:有0~1000的数字,若让每个线程领到10个数字,需在线程拿到数字开始与结束区间时做出判断,看区间是否大于10,大于10则需再次分解。
线程调度场景中ForkJoinPool有专门的抽象类定义,只需重写任务分解逻辑。
ForkJoinTask有多个子类,子类负责分解逻辑。
针对不同场景选择不同子类,定义分解逻辑。//Jdk7 api,后续版本允许方法有返回值
protected void compute() {
if(et - st < unit){ //判断区间是否大于规定值
c.call(st, et); //达到理想分解度,调用业务处理逻辑
}else {
int middle =(st+et)/2; //对任务进行分解
TaskUnit l = new TaskUnit(st, middle,c); //将任务一分为二,变成两个任务
TaskUnit r = new TaskUnit(middle, et,c);
l.fork(); //将任务交由线程调度去执行
r.fork();
l.join();
r.join();
}
}
}
示例采用递归方式分解任务,实现RecursiveAction规范。ForkJoinPool完整示例。
Actor编程模型
Actor模型由Carl Hewitt在几十年前提出,网络中高性能并行处理方案 - 当时还没有这种环境。
如今,硬件和基础设施能力已经赶上,传统面向对象编程(OOP)模型,无法完全构建具有苛刻要求的分布式系统。
OOP核心是封装,规定对象内部数据,不能直接从外部访问,只能通过调用一组特定方法访问或修改,保护封装数据的不变性及安全。
消息传递避免了锁定和阻塞,actor不是方法间的调用,而是互相发送消息。
发送消息不会导致调用方与被调用方处在同一个线程中。
Actor可以连续发送而不会阻塞。因此,可以在单位时间内完成更多操作。
对象在方法返回时,释放对执行线程的控制。
在这方面,actor的行为与对象非常相似,它们在处理完消息时,对消息作出反应并返回执行。
通过这种方式,actor实现了如我们所想的对象调用。
传递消息和调用方法间的区别是:
消息没有返回值,通过消息,actor将工作委托给另一个actor。
正如在调用堆栈时那样,若期望一个返回值,则发送方会被阻塞,或者与被调用的actor处在同一线程上。
相反,actor接收者在回复消息中带着返回结果。