并发设计模式:Actor-based与Task-based

前面一段时间,瞄了一下《七周七并发模型》。感叹还是有很多语言不会,导致有的章节现在还没法看。但是,Actor并发模型还是给我留下了比较深的印象,因为它适合应用于分布式架构,而且具有很好的容错性。和Actor-based相对应的模型就是Task-based。

Actor-based与Task-based的含义

Actor-based和Task-based的含义要从”计算“的内涵说起,”计算“包含两个要素:

  • 用来进行工作的线程
  • 进行工作所需的信息

因为,线程主动地对信息进行计算处理,所以我们把线程作为计算的“主体”,把信息作为计算的“客体”。对应到并发模型中,Actor-based模型注重主体,而Task-based注重客体。

Task-based模式的思想与实现

在Task-based模式中,工作线程之间通过对”客体“的同步操作,并发地完成计算任务。常见的情况下,各个线程引用同一个信息客体,而且所谓的”通信“就是直接调用信息客体类的方法。这种设计模式的主要关注点在于”信息客体“的设计,计算工作的核心部分在”信息客体“的方法中完成。而线程主体只需要调用对应”客体“的方法即可。一个典型的Task-based模式的例子如下:

PriceInfo.java
package ReadWriteLock;
import java.util.concurrent.locks.*;
public class PriceInfo {
    
    private double price1;
    private double price2;
    
    private ReadWriteLock lock;
    
    public PriceInfo() {
        price1 = 1.0;
        price2 = 2.0;
        lock = new ReentrantReadWriteLock();
    }
    
    public double getPrice1() {
        lock.readLock().lock();
        double value = price1;
        lock.readLock().unlock();
        return value;
    }
    
    public double getPrice2() {
        lock.readLock().lock();
        double value = price2;
        lock.readLock().unlock();
        return value;
    }
    
    public void setPrice(double price1, double price2) {
        lock.writeLock().lock();
        this.price1 = price1;
        this.price2 = price2;
        lock.writeLock().unlock();
    }
}
Reader.java
package ReadWriteLock;

public class Reader implements Runnable{
    private PriceInfo priceInfo;
    public Reader(PriceInfo priceInfo) {
        this.priceInfo = priceInfo;
    }
    
    @Override
    public void run() {
        for(int i=0; i <10; i++) {
            System.out.printf("%s: price 1:%f\n", Thread.currentThread().getName(), priceInfo.getPrice1());
            System.out.printf("%s: price 2:%f\n", Thread.currentThread().getName(), priceInfo.getPrice2());
            
        }
    }

}
Writer.java
package ReadWriteLock;

public class Writer implements Runnable{
    private PriceInfo priceInfo;
    public Writer(PriceInfo priceInfo) {
        this.priceInfo = priceInfo;
    }
    
    @Override
    public void run() {
        for(int i=0; i<3; i++) {
            System.out.printf("Writer: Attempt to modify the prices.\n");
            priceInfo.setPrice(Math.random()*10, Math.random()*8);
            System.out.printf("Writer: Prices has been modified.\n");
            try {
                Thread.sleep(2);
            } catch(InterruptedException e) {
                e.printStackTrace();
            }
        }
    }   
}
Main.java
package ReadWriteLock;

public class Main {
    public static void main(String[] args) {
        PriceInfo priceInfo = new PriceInfo();
        Reader readers[] = new Reader[5];
        Thread threadsReader[] = new Thread[5];
        for(int i = 0; i<5; i++){
            readers[i] = new Reader(priceInfo);
            threadsReader[i] = new Thread(readers[i]);
        }
        
        Writer writer = new Writer(priceInfo);
        Thread threadWriter = new Thread(writer);
        
        for(int i=0; i<5; i++)
            threadsReader[i].start();
        threadWriter.start();       
    }
}

Actor-based模式的思想与实现

在Actor-based模式中,线程的作用更加重要,与Task-based模式中通过方法调用的“消息传递”方式相比,Actor-based的消息传递显得更货真价实。Actor-based模式中,客户端方法的调用(invocation)与服务端方法的执行(execution)是分离的,它们之间的联系被抽象为通信。这里的通信可以是线程之间的通信,也可以是机器之间的通信。这就是Actor模式适应于分布式架构的原因。下面的代码片段来自一个典型的使用Actor模式的消费者线程:

public class SchedulerThread extends Thread{
    private final ActivationQueue queue;
    public SchedulerThread(ActivationQueue queue) {
        this.queue = queue;
    }
    
    public void invoke(MethodRequest request) {
        queue.putRequest(request);
    }
    
    public void run() {
        while(true) {
            MethodRequest request = queue.takeRequest();
            request.execute();
        }
    }
}

其实在具体的应用中,这两种模式常常是共存的。个人感觉,面向线程与面向信息各有优劣吧!

发表评论