Статьи

Могу ли я использовать параллельные потоки в контексте транзакции?

Вступление

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

Streams API разработан для правильной работы в соответствии с определенными рекомендациями. На практике, чтобы извлечь выгоду из параллелизма, каждой операции не разрешается изменять состояние общих объектов (такие операции называются без побочных эффектов). При условии, что вы следуете этому руководству, внутренняя реализация параллельных потоков удачно разбивает данные, назначает разные части независимым потокам и объединяет конечный результат.

Первоначально это происходит из-за способа реализации транзакций. В сортировке переменная ThreadLocal используется для маркировки каждого метода, участвующего в транзакции. Переменные ThreadLocal не могут хранить свои значения в параллельном потоке. Чтобы продемонстрировать, что я создал следующий тест

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import org.junit.Assert;
import org.junit.Test;
 
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.IntStream;
 
public class ThreadNameTest {
 
    @Test
    public void threadLocalTest(){
        ThreadContext.set("MAIN");
        AtomicBoolean hasNamechanged = new AtomicBoolean(false);
        IntStream.range(0,10000000).boxed().parallel().forEach(n->{
            if(!"MAIN".equals(ThreadContext.get())){
                hasNamechanged.set(true);
            }
        });
        Assert.assertTrue(hasNamechanged.get());
    }
 
    private static class ThreadContext {
        private static ThreadLocal<String> val = ThreadLocal.withInitial(() -> "empty");
 
        public ThreadContext() {
        }
 
        public static String get() {
            return val.get();
        }
 
        public static void set(String x) {
            ThreadContext.val.set(x);
        }
    }
 
}

Чем выше значение IntStream.range, тем более уверенным будет тест.

Теперь взгляните на этот проект github https://github.com/diakogiannis/transactionplayground/

Проект TransactionPlayground

Я создал сервис, который загружает кошек 4 разными способами

  1. последовательно curl -I -X GET http://localhost:8080/api/cats/all
  2. последовательно, но выдает исключение для создания отметки отката curl -I -X GET http://localhost:8080/api/cats/all-exception
  3. параллельно curl -I -X GET http://localhost:8080/api/cats/all-parallel
  4. параллельно, но выдает исключение для создания отметки отката curl -I -X GET http://localhost:8080/api/cats/all-parallel-exception

Есть также 2 вызова помощника

  1. очистка curl -I -X DELETE http://localhost:8080/api/cats/
  2. и один, чтобы фактически просмотреть curl -X GET http://localhost:8080/api/cats/ кошки curl -X GET http://localhost:8080/api/cats/

Начать проект

пожалуйста, выполните mvn clean package wildfly-swarm:run

Нормальный Заказ

Вызовите curl -I -X GET http://localhost:8080/api/cats/all а затем curl -X GET http://localhost:8080/api/cats/

Нормальный Без Порядка ака Параллель

Вызов для очистки curl -I -X DELETE http://localhost:8080/api/cats/ Вызов curl -I -X GET http://localhost:8080/api/cats/all-parallel а затем curl -X GET http://localhost:8080/api/cats/

Ожидаемый результат — увидеть список кошек. Без заказа. Вот почему параллельный поток обслуживается по принципу «первым пришел — первым пришел» и читает случайным образом из списка.

Нормально с исключением

Вызов для очистки curl -I -X DELETE http://localhost:8080/api/cats/ Вызов curl -I -X GET http://localhost:8080/api/cats/all-exception а затем curl -X GET http://localhost:8080/api/cats/

Ожидаемый результат — пустой список. Это связано с тем, что транзакция была помечена как откат, поэтому транзакция jdbc была откатана, поэтому все записи не были сохранены в базе данных в соответствии с моделью ACID.

Параллельно с исключением

Вызов для очистки curl -I -X DELETE http://localhost:8080/api/cats/ Вызов curl -I -X GET http://localhost:8080/api/cats/all-parallel-exception а затем curl -X GET http://localhost:8080/api/cats/

Ожидаемый результат — не пустой список. Это связано с тем, что каждый поток в параллельном потоке открывает свою собственную транзакцию jdbc и фиксирует ее после завершения. Таким образом, каждый раз, когда вы делаете это, вы выводите на экран некоторых кошек до тех пор, пока не получите исключение и выполнение не остановится. Откат производится только в один поток.

См. Оригинальную статью здесь: Могу ли я использовать параллельные потоки в контексте транзакции?

Мнения, высказанные участниками Java Code Geeks, являются их собственными.