Поведение переменных экземпляра в многопоточности

Я пытаюсь запустить программу с 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 потока. Объект ex — это autowired, поэтому он будет создан только один раз.

Как повлияет значение переменной file?

Я думаю, что один поток попытается установить файл через setFile(file), и он будет изменен другим потоком, прежде чем использовать его в readFile.

Как побороть эту проблему? Как передать значение в многопоточности? Будет ли решена моя проблема, если я сделаю «файл» как volatile?

«Объект ex автоматически подключается, поэтому он будет создан только один раз». Эх, не совсем. Точнее, ThreadExample — это компонент, поэтому он будет создан только один раз.

Michael 04.02.2019 14:09

Кажется, что существует логическое несоответствие между тем, что вы хотите сделать, и тем, как вы хотите это сделать. Когда у вас есть пул потоков, вы, как правило, хотите, чтобы было отправлено несколько новых потоков, а не один элемент, отправленный снова и снова.

posdef 04.02.2019 14:13
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
2
274
2

Ответы 2

Здесь:

@Component
class ThreadExample implements Runnable
{
    String file;

    public void setFile(String file)

И

Object ex is autowired so it will be instantiated only once.

Это не может работать. У вас есть:

executor2.submit(ex);      

Вы передаете объект такой же нескольким задачам, которые должны делать что-то конкретное параллельно. Хуже того, вы вставляете разные значения в этот единственный объект и каким-то волшебным образом ожидаете, что каждая задача увидит именно то значение, которое вы хотели, чтобы она увидела. Что произойдет: эти вещи происходят в разных потоках, поэтому результат (вероятно) совершенно случайный, он же недетерминированный.

Короче говоря: когда у вас есть несколько «вещей», которые нужно сделать с течением времени, вы не можете использовать «одиночный» контейнер для отправки ваших параметров. volatile тут никак не поможет.

Ответ таков: чтобы это работало, ThreadExample не может быть "синглтоном", поэтому аннотация @Component, которая превращает его в синглтон, должна быть убрана. Если это «вступает в противоречие» с другими вашими дизайнерскими идеями, вам придется отступить и переработать свой дизайн.

Re, '...множественные "потоки"...' Вместо того, чтобы использовать пугающие кавычки, вы могли бы назвать их тем, чем они являются --- несколько задания, которые потенциально могут выполняться разными потоками.

Solomon Slow 04.02.2019 14:56

@SolomonSlow Спасибо, хорошая мысль. Адаптировал ответ соответственно.

GhostCat 04.02.2019 15:03

@GhostCat Одно сомнение, каждая переменная, кроме file, является локальной для метода readFile(). Итак, я думаю, что все локальные переменные метода создаются для каждого вызова, даже если объект ex является одноэлементным. Проблема только с file. Исправь меня.

Techie 05.02.2019 05:47

Вы должны сделать 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
    }
}

Затем вместо внедрения bean-компонента ThreadExample в ThreadCall вам нужно внедрить контекст приложения:

@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-компонентом.

Спасибо за ответ. Что произойдет, если я Autowired другой объект в ThreadExample, который является одноэлементным, и он должен быть одноэлементным. Будет ли он вести себя как prototype?

Techie 05.02.2019 05:51

Да, пока вы объявили ThreadExample в качестве прототипа и используете контекст приложения для получения его экземпляра, Spring предоставит вам новый объект для ThreadExample. Также во время создания этого нового объекта он проверит его зависимость, и если вы автоматически подключили одноэлементный компонент, он введет этот одноэлементный компонент в прототип компонента (в данном случае это ThreadExample). В моем последнем проекте у нас были валидаторы, которые были прототипом, и я вводил внутрь однотонные репозитории. Каждый раз, когда я запрашивал новый экземпляр валидаторов из контекста приложения, Spring также вводил репозитории.

whysoseriousson 05.02.2019 07:03

Другие вопросы по теме