Java并发实现

从线程、线程池、ForkJoinPool、Actor框架看java并发。

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接收者在回复消息中带着返回结果。

更多参见actor架构与示例