【问题标题】:Instance variables behaviour in multithreading多线程中的实例变量行为
【发布时间】:2019-02-04 12:53:10
【问题描述】:

我正在尝试运行具有 4 个线程的程序。我想知道线程中实例变量的行为。在这种情况下,我需要将一个值传递给该方法。所以,我这样做是为了传递值并将其用于进一步处理。

@Component
class ThreadExample implements Runnable
{
    String file;

    public void setFile(String file)
    {
       this.file = file;
    }
    @override
    public void run()
    {
       readFile(file);
    }
    public void readFile(String fileName)
    {  
        //logic for reading file
    }
}

@component
class ThreadCall
{
    @Autowired
    ThreadExample ex;

     public void testThread()
     {
        ExecutorService executor2 = Executors.newFixedThreadPool(4);
        getFiles()
          .stream()
          .forEach(f-> {
               ex.setFile(f);
               executor2.submit(ex);                             
           });
     }

}

getFiles() api 返回文件名列表。我正在尝试在Spring 中进行操作。 在这里,4 个线程将同时工作。对象exautowired,所以它只会被实例化一次。

file 变量值会如何影响?

我在想,一个线程会尝试通过setFile(file) 设置文件,然后在readFile 中使用它之前,它会被另一个线程更改。

如何解决这个问题?如何在多线程中传递值? 如果我将'file'设为volatile,我的问题会得到解决吗?

【问题讨论】:

  • "对象 ex 是自动装配的,因此它只会被实例化一次。"嗯,真的不是。更准确地说,ThreadExample 是一个组件,所以它只会被实例化一次。
  • 您想要做的事情和您想要做的事情之间似乎存在逻辑上的不匹配。当你有一个线程池时,你通常希望提交几个新线程,而不是一遍又一遍地提交一个单例

标签: java spring multithreading


【解决方案1】:

这里:

@Component
class ThreadExample implements Runnable
{
    String file;

    public void setFile(String file)

对象 ex 是自动装配的,因此它只会被实例化一次。

那是行不通的。你有:

executor2.submit(ex);      

您将 same 对象传递给应该并行执行特定操作的多个任务。更糟糕的是,您将不同的值推送到该单个对象中,并且以某种方式神奇地期望每个任务都能看到您希望它看到的值。会发生什么:这些事情发生在不同的线程上,所以结果(可能)是完全随机的,也就是不确定的。

长话短说:当您有多个“事情”需要完成时,随着时间的推移,您就不能使用“类似单例”的容器来调度您的参数。 volatile 对此无济于事,一点也不。

答案是:为了使这个工作,ThreadExample 不能是“单例”,所以将其变成单例的 @Component 注释必须消失。如果这样做与您的其他设计理念“冲突”,那么您必须退后一步并重新设计您的设计。

【讨论】:

  • Re, '...multiple "threads"...' 而不是使用吓人的引号,你可以称它们为它们是什么---多个 tasks 可能可以由不同的线程执行。
  • @SolomonSlow 谢谢,好点。相应地调整了答案。
  • @GhostCat 有一个疑问,除了file 之外的每个变量都是readFile() 方法的本地变量。所以,我认为即使对象ex 是单例的,每次调用都会实例化所有方法局部变量。唯一的问题是file。纠正我。
【解决方案2】:

您必须将 ThreadExample 作为原型 bean。

@Component
@Scope("prototype")
class ThreadExample implements Runnable
{
    String file;

    public void setFile(String file)
    {
       this.file = file;
    }
    @Override
    public void run()
    {
       readFile(file);
    }
    public void readFile(String fileName)
    {  
        //logic for reading file
    }
}

那么你必须注入应用程序上下文,而不是在 ThreadCall 中注入 ThreadExample bean:

@Component
class ThreadCall
{
    @Autowired
    ApplicationContext context;


     public void testThread()
     {
        ExecutorService executor2 = Executors.newFixedThreadPool(4);
        getFiles()
          .stream()
          .forEach(f-> {
            ThreadExample ex= context.getBean(ThreadExample.class);   
               ex.setFile(f);
               executor2.submit(ex);                             
           });
     }

}

当你调用这段代码时,基本上每个 ThreadExample 都会是一个新的 bean。

【讨论】:

  • 感谢您的回复。如果我AutowiredThreadExample 中的另一个对象是单例的,它应该是单例的,会发生什么。它的行为会像prototype 吗?
  • 是的,只要您已将 ThreadExample 声明为原型并且您正在使用应用程序上下文获取其实例,spring 将为您提供 ThreadExample 的新对象。同样在创建这个新对象的过程中,它会检查它的依赖关系,如果你已经自动装配了一个单例 bean,那么它会将该单例 bean 注入原型 bean(在这种情况下是 ThreadExample)。在我的上一个项目中,我们有原型验证器,我在其中注入了单吨存储库。每次我从应用程序上下文中询问新的验证器实例时,spring 也在注入 repos
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-20
相关资源
最近更新 更多