0

I have lots of legacy code, which to a large extent consists of classes with following structure:

public interface MyFunctionalBlock
{
    // Setters for the inputs
    void setInput1(final int aInput1);
    void setInput2(final Object aInput2);

    // Inside the method run inputs are converted into results
    void run();

    // If this building block needs functionality from some other building blocks,
    // it gets a reference to them from the Google Guice injector.
    void setInjector(final Injector aInjector);

    // Getters for the results
    long getResult1();
    Object getResult2();
    Map<String,String> getResult3();
}

public class MyFunctionalBlockFactory implements Factory<MyFunctionalBlock>
{
    public MyFunctionalBlock create()
    {
        return new DefaultMyFunctionalBlock();
    }
}

class DefaultMyFunctionalBlock implements MyFunctionalBlock
{
    private int input1;
    private Object input2;
    private long result1;
    private long result2;
    private Map<String,String> result3;
    private Injector injector;

    @Override
    public void run()
    {
        // Here the calculations are performed.

        // If this functional block needs another one, it gets a reference to it using the injector.
        // AnotherFunctionalBlock is the public interface. Implementations of the interface are 
        // intentionally hidden using injector and package-private declaration.
        final AnotherFunctionalBlock fb = injector.getInstance(AnotherFunctionalBlock.class);

        // First, we set the inputs

        fb.setInput1(...);
        fb.setInput2(...);

        [...]

        fb.setInputN(...);

        // Now we run the calculation

        fb.run();

        // Now we can use the results
        fb.getResult1();
        fb.getResult2();

        [...]

        fb.getResultN();
    }

    // Implementation of getters and setters omitted
}

Basically, the entire application consists of such building blocks, which use each other.

Up to now, the application was used in a single-threaded mode. Now I need to modify it such that

  1. the building blocks are thread safe and
  2. the changes to the code, which uses them are minimal (ideally, I would change only the inner workings of the building blocks without touching the public interfaces and calling routines).

How can I do this?

I thought about putting the code from setting the first input to reading the last result into synchronized block (something like the code example below), but it would require rewriting the entire application.

final AnotherFunctionalBlock fb = injector.getInstance(AnotherFunctionalBlock.class);

synchronized(fb)
{
    fb.setInput1(...);
    fb.setInput2(...);

    [...]

    fb.setInputN(...);

    fb.run();
    fb.getResult1();
    fb.getResult2();

    [...]

    fb.getResultN();        
}

Update 1 (09.06.2013 21:57 MSK): A potentially important note - the concurrency stems from the fact that there are N web services, which receive a request, then use the old code to make calculations based on that request and return the results to the web service client.

A potential solution would be to add some sort of queue between web services and the old code.

Update 2:

I thought about how to make my code thread-safe with minimum possible effort and found following solution (currently, I don't care about performance).

There are several web service classes, which all have a backend property and access it concurrently.

public class WebService1
{

  private Backend backend;

  public Response processRequest(SomeRequest1 request)
  {
    return wrapResultIntoResponse(backend.doSomeThreadUnsafeStuff1(request.getParameter1(), request.getParameter2()));
  }
}

public class WebService2
{ 
  private Backend backend;

  public Response processRequest(SomeRequest2 request)
  { 
    return wrapResultIntoResponse(backend.doSomeThreadUnsafeStuff2(request.getParameter1(), request.getParameter2(), request.getParameter3()));
  }
}

All calls to the non-threadsafe code go via the Backend class (all web services reference one and the same Backend instance).

If I ensure that the backend processes one request after another (and never processes two requests simultaneously), I can achieve the desired result without re-writing the entire application.

Here's my implementation of Backend class:

public class Backend
{ 
    private synchronized boolean busy = false;

    public Object doSomeThreadUnsafeStuff1(Long aParameter1, String aParameter2)
    {
        waitUntilIdle();

        synchronized (this)
        {

            busy=true;

            // Here comes the non-thread safe stuff 1

            busy=false;

            notifyAll();
        }
    }

    public Object doSomeThreadUnsafeStuff2(Long aParameter1, String aParameter2, Map<String,String> aParameter3)
    {
        waitUntilIdle();

        synchronized (this)
        {

        busy=true;

        // Here comes the non-thread safe stuff 2

        busy=false;
        notifyAll();
        }
    }
    private void waitUntilIdle()
    {
      while (busy)
      {
        wait();
      }
    } 
}

Can this solution work?

4

3 回答 3

2

除了“使其成为多线程”之外,您还不清楚您要完成什么。Java 中的并发性是一个非常复杂的主题,对于如何将整个应用程序从单线程转换为多线程,您不会找到一个单一的、逐步的答案。如果你这样做,我会完全不相信这个答案。我建议您选择“Java Concurrency in Practice”,这是此类事情的事实上的参考。这就是您将如何学习解决此问题所需的知识。

于 2013-06-09T14:08:23.727 回答
1

您在“更新 2”中描述的内容类似于Actor 模型。如果您 100% 确定自己对性能一点也不关心——我的意思是可能非常糟糕的性能——而且永远不会,那么你建议的是一个公平的解决方案,尽管你提出的实现有问题围绕锁定(或缺乏锁定)busywait(). 查看Akka或其他 Actor 框架可能会为您提供更好的服务。

将 Actor 视为在单个线程中运行并具有 FIFO 队列的东西,您可以将工作单元交付给该队列。对于每个工作单元,Actor 以某种方式对其进行处理,然后发回回复,并且您可以保证工作单元是串行处理的,而不是并行处理的。

您所称的“后端”将是在一个或多个 Actor 中运行的代码,每个 Actor 都与其他 Actor 分开。像这样的框架将允许您采用与您所描述的方法类似的方法,但可以扩展以提高性能,而无需付出太多努力,也无需您管理并发性。

于 2013-06-12T02:39:28.210 回答
1

您在更新 2 中提出的解决方案将使整个应用程序线程安全。我将其总结为在 Web 用户和实际业务代码之间放置一个外观/单例层。遗留代码本身并没有成为线程安全的,您必须为此重写它,但是由于您完全控制对它的访问并以单线程方式执行它,所以总体上没问题。如果对底层业务代码有任何不受控制的访问,它显然会失败。

你说“目前,我不关心性能”。我希望你是对的,因为就锁定争用而言,这是一个可怕的想法。但是,如果您要做的只是将这个不安全的代码公开为 Web 服务,而不提供同时访问,那么是的,在顶部放置一个同步单例外观就可以了。

于 2013-06-11T16:16:18.680 回答