如何使应用程序构建块线程安全?

时间:2013-06-09 13:57:46

标签: java multithreading thread-safety

我有很多遗留代码,它们在很大程度上由具有以下结构的类组成:

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
}

基本上,整个应用程序由相互使用的构建块组成。

到目前为止,该应用程序是以单线程模式使用的。现在我需要修改它,以便

  1. 构建块是线程安全的
  2. 使用它们的代码更改是最小的(理想情况下,我只更改构建块的内部工作方式而不触及公共接口和调用例程)。
  3. 我该怎么做?

    我考虑过将代码设置为第一个输入,将最后一个结果读入synchronized块(类似下面的代码示例),但是需要重写整个应用程序。

    final AnotherFunctionalBlock fb = injector.getInstance(AnotherFunctionalBlock.class);
    
    synchronized(fb)
    {
        fb.setInput1(...);
        fb.setInput2(...);
    
        [...]
    
        fb.setInputN(...);
    
        fb.run();
        fb.getResult1();
        fb.getResult2();
    
        [...]
    
        fb.getResultN();        
    }
    

    更新1(2013年6月9日21:57 MSK):一个可能很重要的注意事项 - 并发源于有N个Web服务接收请求,然后使用旧代码根据该请求进行计算并将结果返回给Web服务客户端。

    一个可能的解决方案是在Web服务和旧代码之间添加某种队列。

    更新2:

    我想过如何以尽可能少的努力使我的代码线程安全并找到以下解决方案(目前,我不关心性能)。

    有几个Web服务类,它们都有后端属性并可以同时访问它。

    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()));
      }
    }
    

    对非线程安全代码的所有调用都通过Backend类(所有Web服务引用同一个Backend实例)。

    如果我确保后端处理一个接一个的请求(并且从不同时处理两个请求),我可以在不重写整个应用程序的情况下实现所需的结果。

    这是我对Backend类的实现:

    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();
          }
        } 
    }
    

    此解决方案可以运作吗?

3 个答案:

答案 0 :(得分:2)

目前还不清楚除了“使其成为多线程”之外你还要做些什么。 Java中的并发性是一个非常复杂的主题,如果将整个应用程序从单线程转换为多线程,您将无法找到单一的逐步解决方案。如果你这样做,我会彻底不信任这个答案。我建议你选择"Java Concurrency in Practice",这是事实上的参考。这就是为了解决这个问题你将如何学习你需要知道的东西。

答案 1 :(得分:1)

您在更新2中提出的解决方案将使整体应用程序成为线程安全的。我将其概括为在Web用户和实际业务代码之间放置facade / singleton层。遗留代码本身变为线程安全,您必须为此重写它,但由于您完全控制对它的访问并以单线程方式执行它,因此您总体上可以。如果对基础业务代码有任何不受控制的访问,那么它显然会失败。

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

答案 2 :(得分:1)

您在“更新2”中描述的内容与Actor model类似。如果你100%确定你不关心一点性能 - 我的意思是潜在的非常糟糕的性能 - 而且永远不会,那么你所建议的是一个公平的解决方案虽然你提出的实现在围绕busywait()的锁定(或缺乏锁定)方面存在问题。通过查看Akka或其他Actor框架,您可能会得到更好的服务。

将Actor视为在单个线程中运行的事物,并且具有可以提供工作单元的FIFO队列。对于每个工作单元,Actor以某种方式处理它然后发送回复,并且保证工作单元是串行处理而不是并行处理。

你称之为“后端”的是在一个或多个Actors中运行的代码,每个Actors都与其他Actors分开。像这样的框架可以让你采用类似于你所描述的方法,但可以扩展以提高性能而不需要太多努力,也不需要你管理并发。